Sunteți pe pagina 1din 220

Introduccin a las Ciencias de la Computacin

(con Java)
Manual de prcticas

Canek Pelez V. Elisa Viso G.


Facultad de Ciencias, UNAM

Introduccin y convenciones
En este libro ofrecemos las prcticas para un primer curso de programacin a nivel licenciatura utilizando el lenguaje de programacin Java. En ese sentido est dirigido a estudiantes de licenciatura que no tengan experiencia programando pero s los conocimientos mnimos para utilizar una computadora moderna. Todo el cdigo mostrado est escrito en Java, y se presenta en el contexto del sistema operativo Linux en particular; aunque cualquier sistema Unix capaz de ejecutar una Mquina Virtual de Java servir tambin. Por ello, los requisitos mnimos de hardware necesarios para realizar las prcticas aqu presentadas son exactamente los mismos que solicita la Mquina Virtual de Java de Sun Microsystems. Todas las prcticas tienen una seccin de desarrollo donde se ofrece un contexto terico para llevar a cabo los ejercicios. Si el estudiante y/o profesor consideran que la teora vista en clase es suciente para llevar a cabo cada prctica, el alumno puede sin ningn problema saltarse las secciones de desarrollo y resolver los ejercicios y preguntas directamente; pero creemos que la teora incluida en cada prctica ser de ayuda para el estudiante que decida consultarla. Se ha tratado de utilizar una serie de convenciones coherentes para poder distinguir los diferentes contextos en los que aparece cdigo en el texto. Todo el cdigo fue insertado en el texto, en el contexto del procesador de palabras A X, utilizando el paquete listings, de Carsten Heinz, a menos que el paquete fuera LTE incapaz de manejar el contexto. De cualquier forma, si no se poda usar el paquete listings, se trat de emular su funcionamiento. El cdigo que aparece dentro de bloques con lneas numeradas es cdigo que est pensado para que se escriba directamente en un archivo o que est sacado de algn archivo ya existente. Por ejemplo
5 6 7 8 9 10 public class UsoMatriz2x2 { public s t a t i c void main ( S t r i n g [ ] args ) { Consola c ; c = new Consola ( "Matrices" ) ; ...

El cdigo que aparece en cajas y sin numeracin de lneas es cdigo pensado para

ii ejemplos cortos, no necesariamente existente o atado a alguna funcin en particular. Por ejemplo
c . i m p r i m e l n ( "hola mundo" ) ;

Los comandos pensados para que el alumno los teclee en su intrprete de comandos estn escritos utilizando el tipo typewriter, y se utiliza el carcter # para representar el prompt del intrprete. Por ejemplo
# javac -classpath interfaz1.jar:. Matriz2x2.java

Cada vez que aparece un nuevo concepto en el texto, se resalta utilizando el tipo slanted. Por ejemplo: Generalmente diremos que la clase principal o clase de uso es la clase que mandamos ejecutar desde el intrprete de comandos con java. Cuando un nombre o nombres aparezca entre < y >, signica que puede ser reemplazado por cadenas proporcionadas por el alumno. Por ejemplo
# java <NombreDeClase>

signica que <NombreDeClase> puede ser reemplazado por cualquier nombre de clase. Tambin signicar que puede ser reemplazado por ms de un trmino, si el contexto lo permite. Por ejemplo en
# javac <VariosArchivosDeClases>

<VariosArchivosDeClases> podr ser reemplazado por varios archivos de clases.

ndice general
1. Ant y el compilador de Java 2. Usar y modicar clases 3. Variables, tipos y operadores 4. Interfaces y clases por dentro 5. Estructuras de control y listas 6. Herencia 7. Entrada/salida y arreglos 8. Recursin 9. Manejo de excepciones 10. Interfaces grcas 11. Ant y archivos Jar 12. *Hilos de ejecucin y enchufes A. El resto de las leyes 1 7 21 41 71 87 103 121 129 143 181 191 209

Prctica: Ant y el compilador de Java

Every non-trivial program has at least one bug. Corollary 1 - A sufcient condition for program triviality is that it have no bugs. Corollary 2 - At least one bug will be observed after the author leaves the organization. Murphys Laws of Computer Programming #1

Meta
Que el alumno comience a utilizar Ant para compilar, detectar errores de sintaxis y semnticos, y generar bytecode ejecutable.

Desarrollo
Java es un lenguaje compilado, lo que quiere decir que un compilador se encarga de transformar las instrucciones de alto nivel de un programa, en cdigo que la Mquina Virtual de Java (Java Virtual Machine o JVM) puede ejecutar.

Actividad 1.1 Invoca al compilador de Java sin ningn argumento, con la siguiente lnea de comandos: # javac (Nota que slo debes escribir javac y despus teclear Enter ; el # slo representa el prompt de tu intrprete de comandos). Anota todas las opciones que se pueden pasar al compilador.

El compilador de Java no slo genera el cdigo ejecutable por la JVM; tambin detecta errores de sintaxis y semnticos, mostrando en qu lnea ocurren. Adems, el compilador tiene lo que se conoce como recuperacin; al encontrar el primer error no se detiene, trata de continuar tanto como sea posible y seguir encontrando errores. Esto ltimo es importante porque le permite al programador corregir un mayor nmero de errores por cada compilacin, en lugar de tener que compilar cada vez que quiere encontrar el siguiente error.

Ant
Es posible compilar cualquier proyecto de Java utilizando el compilador desde la lnea de comandos. Sin embargo, conforme un proyecto crece en tamao y complejidad, la lnea de comandos va siendo cada vez ms restrictiva. Para ayudarnos con la compilacin de proyectos en Java, utilizaremos Ant. Ant es un programa (escrito en Java, por cierto), que lee un archivo de conguracin en XML, y de ah obtiene la informacin necesaria para compilar un proyecto.

Actividad 1.2 Visita la pgina del proyecto Ant, en http://ant.apache.org/

Ant y el compilador de Java Actividad 1.3 De la pgina de las prcticas1 , baja el archivo practica1. tar .gz, y descomprmelo con la siguiente lnea de comandos: # tar zxvf practica1.tar.gz Despus, trata de compilar la prctica: # cd practica1 # ant compile Cuntos errores marca? Los entiendes? El signicado de cada error est dado por la traduccin del ingls.

El compilador de Java, a travs de Ant, muestra en qu lnea de un programa ocurre el error (si encuentra alguno). Si se utiliza un editor como XEmacs, y si est congurado de manera adecuada, se puede compilar dentro de XEmacs y saltar directamente a la lnea en donde ocurre el error.

Actividad 1.4 Abre el archivo UsoReloj.java en XEmacs (est en el directorio icc1/practica1), y compila haciendo C-c C-v C-b , tecleando compile y dando Enter despus. Debe abrirse un segundo buffer en XEmacs donde se muestran los errores encontrados por el compilador. Cmbiate a ese buffer haciendo C-x o y teclea Enter en la primera lnea que marque error. Qu sucede? En caso de que no funcione, puedes intentar cargar el archivo de proyecto que utiliza la prctica. Para esto, en XEmacs haz click en el men JDE Project Project File Load. Si no existe ningn men JDE, tu XEmacs no est congurado como es debido.

Una vez que un programa est libre de errores, el compilador genera un archivo en bytecode. La JVM ejecuta bytecode, un formato binario desarrollado para que los ejecutables de Java puedan ser utilizados en varias plataformas sin necesidad de recompilar. El bytecode puede copiarse directamente a cualquier mquina que tenga una JVM, y ejecutarse sin cambios desde ah. A eso se le conoce como portabilidad a nivel binario. Muchos otros lenguajes de programacin tienen portabilidad a nivel de cdigo, y otros no tienen ningn tipo de portabilidad.
1

La pgina de las prcticas est disponible en http://abulaa.fciencias.unam.mx/practicas.

El archivo build.xml
Ant utiliza un archivo de conguracin escrito en XML. XML es el Lenguaje Extendible para el Formato de Documentos (Extensible Markup Language en ingls), y es, sencillamente, una manera de representar informacin.

Actividad 1.5 Busca en la red informacin acerca de XML. Puedes comenzar en http://www.w3c.org

Para motivos de esta prctica, slo es necesario saber que un documento XML tiene etiquetas (tags), que cada etiqueta tiene una etiqueta de inicio (del tipo <etiqueta>) y una etiqueta nal (del tipo </etiqueta>), y que estas etiquetas estn anidadas:
1 <etiqueta1> 2 <etiqueta2> 3 ... 4 </ etiqueta2> 5 <etiqueta3> 6 <etiqueta2> . . . </ etiqueta2> 7 </ etiqueta3> 8 ... 9 </ etiqueta1>

Esto es ilegal en XML:


< e t i q u e t a 1 >< e t i q u e t a 2 > . . . < / e t i q u e t a 1 >< / e t i q u e t a 2 >

No est anidado. Lo correcto es:


< e t i q u e t a 1 >< e t i q u e t a 2 > . . . < / e t i q u e t a 2 >< / e t i q u e t a 1 >

En XML, es equivalente escribir <et></et> a <et/>, para representar etiquetas vacas. Todas las etiquetas de un documento XML pueden tener atributos, que son de la forma:
< e t i q u e t a a t r i b u t o 1 ="valor1" a t r i b u t o 2 ="valor2">

La informacin que representa el archivo build.xml es la necesaria para manejar un proyecto en Java a travs de Ant. Por ello, la etiqueta raz del archivo build.xml (la etiqueta que envuelve a todas las dems), se llama project (proyecto).

Ant y el compilador de Java

En el archivo build.xml de esta prctica, dentro de la etiqueta raz slo hay etiquetas target (objetivo). Cada objetivo es una tarea que Ant puede ejecutar al ser llamado. Los objetivos de nuestro archivo build.xml son: compile run docs clean Compila la prctica. Ejecuta la prctica, compilndola si no ha sido compilada. Genera la documentacin JavaDoc de la prctica (veremos esto la prxima prctica). Limpia la prctica de bytecode, documentacin, o ambos.

Para decirle a Ant que ejecute un objetivo, slo se lo pasamos como parmetro (tienen que hacer esto en el directorio donde est el archivo build.xml):
# ant compile # ant run

Si no se le pasa ningn objetivo a Ant, se ejecutar compile. Esto es porque la etiqueta project del archivo build.xml tiene un atributo llamado default, cuyo valor es compile:
2 < p r o j e c t name="practica1" d e f a u l t ="compile" b a s e d i r =".">

La sintaxis del archivo build.xml la iremos analizando en las siguientes prcticas del curso.

Ejercicios
1. Corrige los errores que aparecen en la prctica, hasta que el programa compile y genere el bytecode. Utiliza los mensajes del compilador2 para determinar la lnea del error y en qu consiste. Todos los errores estn en la clase UsoReloj, que est en el archivo UsoReloj.java (las clases de Java generalmente estn en un archivo llamado como la clase, con extensin .java). No toques los dems archivos. (Puedes darle una mirada a los dems archivos de la prctica; sin embargo, el funcionamiento de estos archivos ser discutido posteriormente.) 2. Una vez que el programa compile, ejectalo con la siguiente lnea de comandos (estando en el directorio practica1):
2

S, los mensajes estn en ingls. Ni modo.

6
# ant run

3. Ya que tu prctica compile y se ejecute como es debido, haz


# ant clean

para borrar los archivos que se hayan compilado. Despus haz


# ant

y ve cuntos archivos dice ant que va a compilar (al momento de correr Ant con el objetivo compile, dice cuntos archivos se prepara a compilar). Una vez que termine de compilar, ejecuta el comando
# touch src/icc1/practica1/UsoReloj.java

y despus vuelve a correr Ant. Notas alguna diferencia? Explcala.

Preguntas
1. Qu errores encontraste al compilar esta prctica? Explica en qu consisten. 2. Los errores que encontraste, de qu tipo crees que sean, sintcticos o semnticos? Justica tu respuesta. 3. Cuntos archivos en bytecode (los que tienen extensin .class) se generaron? 4. Cul crees que sea la explicacin del comportamiento de Ant despus de hacer el ejercicio 3? Justica tu respuesta.

Prctica: Usar y modicar clases

Bugs will appear in one part of a working program when another unrelated part is modied. Murphys Laws of Computer Programming #2

Meta
Que el alumno aprenda cmo funcionan las clases, cmo se construyen objetos de una cierta clase, qu son mtodos y variables de clase, y cmo se invocan las funciones de una clase.

Objetivos
Al nalizar la prctica el alumno ser capaz de:

8 entender qu es una clase; entender qu es una interfaz; declarar y construir objetos con el operador new; entender qu son los mtodos y variables de una clase; hacer que el objeto llame a las funciones de su clase con el operador . (punto); modicar las funciones de una clase para que sus objetos tengan un comportamiento distinto; y generar la documentacin de una clase para saber qu funciones provee sin necesidad de leer el cdigo directamente.

Desarrollo
En la prctica pasada, aprendimos cmo utilizar Ant y el compilador de Java para generar bytecode ejecutable, y ejecutamos un programa que mostraba un reloj en la pantalla. En esta prctica analizaremos ms detenidamente la clase UsoReloj para ver cmo funciona el programa, y modicaremos ciertas partes de la clase ClaseReloj.

Clases
Las clases de la prctica pasada son ClaseReloj, VistaRelojAnalogico y UsoReloj. Adems tenemos dos interfaces: Reloj y VistaReloj. Veremos las interfaces en detalle ms adelante en esta misma prctica. Los objetos de la clase ClaseReloj son como la maquinaria de un reloj de verdad. La maquinaria es la que se encarga de que el reloj funcione de cierta manera (de que avance o retroceda, etc.) Los objetos de la clase VistaRelojAnalogico son como la parte externa de un reloj de verdad. Piensen en el Big Ben; los objetos de la clase VistaRelojAnalogico son la torre, las manecillas y la cartula con los nmeros. En otras palabras, los objetos de la clase VistaRelojAnalogico no saben cmo funciona un reloj (ni tienen porqu saberlo). Slo se encargan de mostrar la informacin del reloj al mundo exterior, poniendo las manecillas donde deben estar. La clase UsoReloj es como el dueo de un reloj de verdad. Posee al reloj, lo pone a la hora requerida y en general hace con l todo lo que se puede hacer con un reloj. La clase principal del programa es UsoReloj, en el sentido de que es en esta clase donde se utilizan las otras dos (ClaseReloj y VistaRelojAnalogico). Generalmente diremos que la clase principal o clase de uso es la clase que mandamos ejecutar desde el intrprete de comandos con java o con ant run.

Usar y modicar clases

La clase principal siempre tendr el punto de entrada a nuestros programas; este punto de entrada es la funcin main.1

Cmo Ant compila y ejecuta programas


En la prctica anterior compilamos nuestro programa con:
# ant compile

Tambin as compilaremos esta prctica.

Actividad 2.1 De la pgina del libro, baja el archivo practica2.tar.gz y descomprmelo igual que el de la prctica anterior. Compila la prctica.

Si abrimos el archivo build.xml (que es prcticamente igual al de la prctica anterior), notaremos que el objetivo compile est denido de la siguiente manera:
4 5 6 7 8 < t a r g e t name="compile"> <mkdir d i r ="build" / > < j a v a c s r c d i r ="src" d e s t d i r ="build" debug="true" d e b u g l e v e l ="source" / > </ target>

Lo primero que hace este cdigo es crear un directorio (dentro del directorio donde se encuentra el archivo build.xml). Este directorio (build) sirve para que ah se guarde el bytecode que se genera al compilar nuestro cdigo. El tener dos directorios separados para nuestro cdigo fuente (src, por source en ingls, fuente, origen), y otro para nuestros archivos construidos (build, por construir en ingls), nos permite una ms fcil organizacin de nuestro cdigo. En particular, limpiar nuestro directorio de trabajo se limita a borrar el directorio build. Para crear el directorio, utilizamos la tarea mkdir (las tareas son etiquetas que Ant provee para hacer cosas):
5
1

<mkdir d i r ="build" / >

Los trminos funcin, mtodo, y (aunque cada vez ms en desuso) procedimiento sern considerados iguales en sta y las dems prcticas del curso. En orientacin a objetos suele hacerse hincapi en que son mtodos, no funciones, pero nosotros relajaremos esa regla ya que en Java slo existen mtodos y no hay rezn para diferenciarlos de lo que en otros lenguajes son las funciones.

10

Noten que la etiqueta termina con / >, lo que quiere decir que no tiene cuerpo. Despus de crear el directorio build, mandamos llamar al compilador de Java, javac, dicindole dnde est nuestro cdigo fuente (srcdir), dnde queremos que se guarden los binarios (destdir), que queremos que el cdigo tenga smbolos de depuracin (debug), y el nivel de depuracin que queremos (debuglevel). Para esto, utilizamos la tarea javac:
6 7 < j a v a c s r c d i r ="src" d e s t d i r ="build" debug="true" d e b u g l e v e l ="source" / >

Tambin es una etiqueta sin cuerpo. Con esta informacin, Ant busca los archivos fuente (.java) en el directorio de fuentes, o en sus subdirectorios, y la versin compilada la deja en el directorio build. Para ejecutar nuestro programa utilizamos
# ant run

El objetivo run est denido de la siguiente manera:


9 10 11 12 13 14 15 < t a r g e t name="run" depends="compile"> < j a v a classname="icc1.practica2.UsoReloj"> <classpath> <pathelement path="build" / > < / classpath> < / java> </ target>

El objetivo tiene un atributo, depends, cuyo valor es compile. Esto quiere decir que el objetivo run depende del objetivo compile. En otras palabras, el objetivo run no puede ser llamado si antes no ha sido llamado el objetivo compile. Si llamamos al objetivo run sin haber llamado al objetivo compile, Ant automticamente lo llamar. Para ejecutar el programa, utilizamos la tarea java:
10 11 12 13 14 < j a v a classname="icc1.practica2.UsoReloj"> <classpath> <pathelement path="build" / > < / classpath> < / java>

La tarea tiene un atributo, classname, cuyo valor es icc1.practica2.UsoReloj. Este atributo le dice a Ant qu clase queremos ejecutar; en este caso UsoReloj. Lo que precede a UsoReloj, icc1.practica2, es el paquete de la clase UsoReloj. Veremos qu son exactamente los paquetes ms adelante.

Usar y modicar clases

11

(La diferencia entre el build.xml de esta prctica y la anterior, es que el anterior tena como valor del atributo classname a icc1.practica1.UsoReloj). Esta es la primera tarea que s tiene cuerpo. El cuerpo es
11 12 13 <classpath> <pathelement path="build" / > < / classpath>

El cuerpo es una estructura de tipo ruta (path en ingls). En otras palabras, le dice a Java dnde buscar las clases necesarias para la ejecucin de un programa. Esta estructura a su vez tiene un cuerpo que est formado por la etiqueta pathelement, que en su atributo path dice qu ruta queremos utilizar. En este caso es el directorio build, donde estn nuestras clases compiladas. La etiqueta classpath puede aceptar varios directorios en su cuerpo.

El mtodo main
Acabamos de ver cmo ejecutar un programa en Java utilizando Ant. Lo que hace la JVM al ejectuar una clase es ver el bytecode de la clase llamada (UsoReloj en nuestra prctica), buscar la funcin main dentro de la misma, y ejecutarla. Si no existe un mtodo main en esa clase, la JVM termina con un mensaje de error. Por eso se le llama punto de entrada a la funcin main; es lo primero que se ejecuta en un programa en Java. Todas las clases en Java pueden tener su propio mtodo main; sin embargo, en nuestras prcticas generalmente slo una clase tendr mtodo main. El mtodo main de una clase se ejecuta nicamente cuando la clase es invocada desde el sistema operativo (shell).

Actividad 2.2 Abre el archivo UsoReloj.java en XEmacs y localiza la funcin main.

Noten que todas las partes de una clase de Java estn formadas a partir de bloques, anidados unos dentro de otros, y todos adentro del bloque ms grande, que es el de la clase misma. Un bloque comienza con un {, y termina con un }. Si descontamos el bloque de la clase, todos los bloques son de la forma:
{ < e x p r e s i n 1 >; < e x p r e s i n 2 >; ... < e x p r e s i n N> ;

12

Esto es un bloque de ejecucin. En l, las expresiones del bloque se ejecutan en el orden en que aparecen. A un bloque tambin se le suele llamar cuerpo. Cada vez que un bloque comienza, la sangra del programa debe aumentar; esto facilita la lectura de cdigo.

Declaracin y construccin (instancing) de objetos


Ya dijimos que la clase UsoReloj es como el dueo de un reloj de verdad. Pero antes de poder usarse un reloj, debe adquirirse. Para poder adquirir un objeto de la clase ClaseReloj, se tiene que declarar. Declarar un objeto de una cierta clase es como decir en qu mano se va a usar o asignarle un cajn donde se va a guardar. La declaracin es simplemente la reserva de cmo se va a localizar al reloj una vez que se tenga. En la clase UsoReloj lo declaramos as:
14 Reloj r e l ;

Podramos hacerlo con


14 ClaseReloj r e l ;

pero queremos utilizar la interfaz Reloj. Ahorita veremos en qu consisten las interfaces. As queda declarado un objeto que implementa la interfaz Reloj, y el nombre con el que haremos referencia al objeto ser rel. Declarar un objeto es como cuando llenamos un catlogo para hacer compras por correo o por Internet; hacemos explcito qu queremos (en este caso un objeto que implementa la interfaz Reloj), pero an no lo tenemos. Para poseerlo, necesitamos crearlo. Para crear o construir (instanciar)2 un objeto, se utiliza el operador new. En la clase UsoReloj lo hacemos de la siguiente manera:
17 r e l = new C l a s e R e l o j ( ) ;

El operador new llama al constructor de la clase ClaseReloj. Veremos con ms detalle a los constructores en la siguiente prctica. Es hasta el momento de consruir el objeto cuando ste comienza a existir propiamente; ahora podemos comenzar a utilizarlo.
2

Del ingls instancing, aunque la palabra en espaol no es del todo adecuada

Usar y modicar clases Actividad 2.3 En el archivo UsoReloj.java, en su funcin main, localiza dnde se declara al objeto rel que implementa la interfaz Reloj, y tambin dnde se construye un objeto de la clase ClaseReloj.

13

Dentro de la funcin main de la clase UsoReloj ya tenemos un objeto de la clase ClaseReloj que se llama rel. Pero dijimos que los objetos de la clase ClaseReloj son como la maquinaria de un reloj. Necesitamos la caja, la parte externa. Para poner a la maquinaria la cartula y las manecillas, necesitamos un objeto que implemente la interfaz VistaReloj. De igual manera, necesitamos declarar al objeto y construirlo. En la clase UsoReloj hacemos esto:
V i s t a R e l o j rep ; ... rep = new V i s t a R e l o j A n a l o g i c o ( r e l ) ;

Y de esta manera ya tenemos un objeto de la clase VistaRelojAnalogico llamado rep. Noten que el constructor de la clase VistaRelojAnalogico no es igual al de la clase ClaseReloj. El constructor de la clase ClaseReloj no tena parmetros:
r e l = new C l a s e R e l o j ( )

mientras que el constructor de la clase VistaRelojAnalogico tiene un parmetro


rep = new V i s t a R e l o j A n a l o g i c o ( r e l )

El parmetro es rel; veremos con ms detalle los parmetros en las siguientes prcticas. La construccin de objetos se puede leer as: al construir un objeto de la clase VistaRelojAnalogico le pasamos como parmetro un objeto de la clase ClaseReloj. Esto es como si atornillramos la maquinaria del reloj a la caja; hacemos que la representacin del reloj est atada a su funcionamiento interno (y tiene sentido que sea en ese orden, ya que la caja de un reloj no tiene razn de ser sin el mecanismo)3 .
Aqu algunos de ustedes estarn pensando que no tiene mucho sentido separar la maquinaria de la caja; despus de todo, cuando uno compra un reloj lo hace con la maquinaria y la caja juntas (aunque muchos relojes nos vienen con varios extensibles intercambiables, por ejemplo). Esta es una de las caractersticas del paradigma orientado a objetos. Al tener separadas la parte que muestra al reloj de la parte que maneja su funcionamiento, es posible cambiar la representacin del reloj sin tener que reescribir todo el programa (podramos escribir una clase llamada VistaRelojDigital, que en lugar de usar manecillas utilizara el formato HH:MM:SS para representar el reloj, y ya no tenemos que preocuparnos del funcionamiento interno del reloj, que lo maneja la clase ClaseReloj).
3

14

Uso de mtodos
Los mtodos o funciones de una clase son los servicios a los cuales un objeto de esa clase puede tener acceso. En la clase UsoReloj, el objeto rel de la clase ClaseReloj slo utiliza cuatro mtodos: avanzaSegundo, avanzaMinuto, avanzaHora y ponTiempo. Para llamar a una funcin utilizamos el operador . (punto). En la clase UsoReloj, llamamos a avanzaSegundo de la siguiente manera:
22 r e l . avanzaSegundo ( ) ;

En general, para llamar una funcin se utiliza:


<nombreObjeto >. < nombreFuncin> ( < parmetro_1 > , . . . , < parmetro_N > ) ;

Lo que hace entonces el operador . (punto) es ver de qu clase es el objeto (un objeto siempre sabe de qu clase es ejemplar4 ), buscar la funcin por su nombre, y ejecutarla pasndole los parmetros.

Actividad 2.4 En el archivo UsoReloj.java, busca dnde se llama a las funciones avanzaSegundo, avanzaMinuto y avanzaHora y ponTiempo de la clase ClaseReloj.

Estos mtodos hacen lo que su nombre indica; avanzaSegundo hace que el secundero se mueva hacia adelante un segundo. Las otras dos (avanzaMinuto y avanzaHora) hacen lo mismo con el minutero y las horas respectivamente. El mtodo ponTiempo cambia el valor del secundero, del minutero y de las horas de un solo golpe. Recibe tres enteros como parmetro, as que para poner el reloj a las 7 con 22 minutos y 34 segundos tenemos que hacer esto:
r e l . ponTiempo ( 7 , 2 2 , 3 4 ) ;

Ahora, una de las desventajas de separar el funcionamiento del reloj de su representacin, es que esta ltima no cambia automticamente cuando el estado del reloj cambia. As que explcitamente tenemos que decirle a la representacin (a la caja) que se actualice. Para esto, la clase VistaRelojAnalogico nos ofrece el mtodo que se llama actualiza, que no recibe parmetros:
23
4

rep . a c t u a l i z a ( ) ;

Usamos el trmino ejemplar en lugar de la traduccin literal instancia, que viene del ingls instance, ya que instancia tiene otro signicado en espaol.

Usar y modicar clases

15

En la funcin main de la clase UsoReloj, utilizamos tambin la funcin espera de la clase VistaRelojAnalogico. Esta funcin slo espera un determinado nmero de segundos (que es el parmetro que recibe) antes de que nada ms ocurra.
Actividad 2.5 Abre el archivo ClaseReloj.java y busca la denicin de las funciones utilizadas en UsoReloj.java. Si ests utilizando XEmacs como editor, puedes abrir el archivo y hacer C-s para comenzar una bsqueda interactiva. Esto quiere decir que mientras vayas tecleando, XEmacs ir buscando en el archivo lo que vayas escribiendo. Prubalo; abre el archivo ClaseReloj.java, haz C-s y teclea avanzahora (as, todas minsculas). Para cuando hayas terminado, XEmacs estar marcando la primera ocurrencia de la cadena avanzaHora en el archivo (observa que XEmacs ignora la diferencia entre maysculas y minsculas). Esa primera ocurrencia no es todava la denicin del mtodo avanzaHora; si vuelves a dar C-s , XEmacs marcar ahora s la denicin que buscas. Utiliza C-s cuando quieras buscar algo rpido en XEmacs.

Variables locales y de clase


En la funcin main de la clase UsoReloj, declaramos dos variables, r y rep; la primera para referirnos a nuestro objeto de la clase ClaseReloj, y la segunda para referirnos a nuestro objeto de la clase VistaRelojAnalogico. En la clase ClaseReloj, puedes ver al inicio del archivo las siguientes lneas:
20 21 22 p r i v a t e i n t hora ; p r i v a t e i n t minuto ; p r i v a t e i n t segundo ;

Tambin hora, minuto y segundo son variables. Qu diferencia tienen de r y rep? La primera diferencia es obvia; la declaracin de hora, minuto y segundo est precedida por un private. La palabra clave private es un modicador de acceso; los veremos con ms detalle en la prctica 3. La segunda diferencia tambin es muy fcil de distinguir: r y rep estn declaradas dentro de una funcin (main en este caso), mientras que hora, minuto y segundo estn declaradas dentro de la clase. La diferencia es importante; las variables declaradas dentro de una funcin son variables locales, mientras que las variables declaradas dentro de una clase son variables de clase o variables miembro.

16 Desde que vieron la declaracin de hora, minuto y segundo, debi ser obvio que sirven para que guardemos los valores del mismo nombre de nuestros objetos de la clase Reloj. Las variables de clase sirven justamente para eso; para guardar el estado de un objeto. Al valor que tienen en un instante dado las variables de clase de un objeto se le llama el estado de un objeto. Veremos ms acerca de las variables en las siguientes prcticas.

Mtodos desde adentro


Ya vimos algunos mtodos de la clase ClaseReloj que se utilizan en la clase UsoReloj. Para qu sirven los mtodos? Dijimos que las variables de clase guardan el estado de un objeto. La misin de los mtodos de una clase es justamente cambiar ese estado, o para obtener informacin acerca de l de alguna manera. Por ejemplo, las funciones avanzaSegundo, avanzaMinuto y avanzaHora cambian el estado de nuestro objeto r, y la funcin actualiza cambia el estado de nuestro objeto rep, para que mueva nuestras manecillas a la hora actual.5 Veamos el mtodo avanzaHora de la clase ClaseReloj (recuerda que est en el archivo ClaseReloj.java):
151 152 153 154 155 156 public void avanzaHora ( ) { t h i s . hora ++; i f ( t h i s . hora > R e l o j . HORAS_POR_DIA) { t h i s . hora = 0 ; } }

Qu hace el mtodo? En primer lugar, no recibe ningn parmetro. Esto tiene sentido, ya que la funcin siempre har lo mismo (avanzar una hora), y por lo tanto no necesita ninguna informacin externa para hacerlo. Despus, est la lnea:
152 t h i s . hora++

El operador ++ le suma un 1 a una variable. Veremos ste y ms operadores en las siguientes prcticas. Sumarle un 1 a hora es justamente lo que queremos (que las horas avancen en uno). Pero, qu ocurre cuando son las 12 y le sumamos 1? En un reloj de verdad, la hora da la vuelta y se pone de nuevo en 1. En particular en nuestra clase ClaseReloj estamos suponiendo que 12 es igual a cero (como ocurre con muchos relojes digitales),
Algunas veces tambin se utilizan funciones que no cambian el estado de un objeto, ni tampoco lo obtienen; el mtodo espera es un ejemplo.
5

Usar y modicar clases

17

y entonces cuando hora vale 0, la manecilla de las horas apunta hacia el 12. As que cuando hora valga 11 y se llame a avanzaHora, nuestro reloj deber poner hora en 0. Eso es justamente lo que hacen las lneas
153 154 155 i f ( t h i s . hora > R e l o j . HORAS_POR_DIA) { t h i s . hora = 0 ; }

La condicional if verica lo que hay dentro de su parntesis, y si es verdad, ejecuta el bloque que le sigue. Este if puede leerse si hora es mayor que once, entonces haz que hora valga cero. Veremos con ms detalle a if y otras condicionales ms adelante. Las dems funciones de la clase ClaseReloj que hemos visto hasta ahora funcionan de manera similar a avanzaHora. Deniremos nuestras propias funciones en prcticas siguientes.

Interfaces
Hemos estado trabajando con la clase ClaseRejoj; sin embargo, el objeto que declaramos fue de la interfaz Reloj. Qu es una interfaz?

Actividad 2.6 Abre y examina el contenido de la interfaz Reloj, en el archivo Reloj.java.

Si examinamos el archivo Reloj.java, notaremos que una interfaz es una especie de clase vaca. Sus mtodos no tienen cuerpo; no hacen nada. Para qu nos sirve entonces? Una interfaz es un contrato. Especica qu ofrece una clase; pero no dice cmo lo hace. La clase ClaseReloj implementa (implements) la interfaz Reloj. Con eso, se compromete a cumplir el contrato; a encontrar un modo de hacer que funcionen los mtodos que declara la interfaz. Veremos con ms detalle a las interfaces cuando veamos herencia.

Comentarios dentro de los programas


En las clases ClaseReloj y UsoReloj, habrs visto varias partes del cdigo que empezaban con //, o que estaban encerradas entre /* y */ . A estas partes se les llama comentarios, y sirven para que el programador haga anotaciones acerca de lo que est escribiendo. El compilador ignora los comentarios; sencillamente se los salta.

18 Los comentarios que empiezan con // son de una lnea. El compilador ignora a partir de //, y lo sigue haciendo hasta que encuentra un salto de lnea o que acaba el archivo, lo que ocurra primero. Los comentarios que empiezan con /* son por bloque. El compilador ignora absolutamente todo lo que encuentra a partir de /* , y lo sigue haciendo hasta que encuentre un */ . No se vale tener comentarios de este tipo anidados; esto es invlido:
/* / * Comentario i n v l i d o ( e s t anidado ) . * / / *

Tambin es invlido abrir un comentario /* y no cerrarlo nunca. Si el compilador ve un /* , y despus llega al n del archivo sin encontrar un */ , entonces marcar un error.

JavaDoc
Cmo podemos saber qu mtodos nos ofrece una clase? La manera ms sencilla es abrir el archivo de la clase y leer el cdigo. Sin embargo, esto puede resultar tedioso, si la clase es larga y compleja. Java ofrece un pequeo programa que nos permite observar los interiores de una clase de manera ordenada y elegante. El programa se llama JavaDoc, y lee programas escritos en Java, con lo que genera pginas HTML que podemos ver utilizando cualquier navegador, como Firefox o el Internet Explorer. Como casi todo lo que tiene que ver con desarrollo en Java, Ant tiene una tarea para manejar a JavaDoc, y sorprendentemente se llama javadoc. En nuestro build.xml lo mandamos llamar dentro del objetivo docs:
1 2 3 4 < t a r g e t name="docs"> <javadoc sourcepath ="src" d e s t d i r ="docs" packagenames="icc1.practica2" / > </ target>

Lo nico que le decimos a la tarea javadoc es dnde estn los archivos fuente, en qu directorio queremos que genere la documentacin, y de qu paquetes queremos que la genere (vimos un poco arriba que el paquete que estamos utilizando es icc1.practica2.

Usar y modicar clases Actividad 2.7 Invoca a JavaDoc sin parmetros con la siguiente lnea: # javadoc Anota todas las opciones que se podran utilizar.

19

Actividad 2.8 Genera la documentacin de la prctica con el comando # ant docs Con ayuda de tu ayudante revisa la documentacin generada.

La documentacin generada muestra, entre otras cosas, los mtodos y constructores de las clases, incluyendo los parmetros que reciben. Ms adelante veremos cmo utilizar los comentarios de Java para ayudar a JavaDoc a generar informacin.

Ejercicios
1. Modica la funcin avanzaMinuto de la clase ClaseReloj para que cada vez que sea llamada, en lugar de avanzar un minuto avance diez. Recuerda que la clase ClaseReloj est en el archivo ClaseReloj.java. Compila y ejecuta de nuevo el programa para ver si tus cambios son correctos. 2. Con la documentacin que generaste, lee qu parmetros recibe el segundo constructor de la clase VistaRelojAnalogico, y utilzalo en el mtodo main de la clase UsoReloj, para que al ejecutar el programa la ventana del reloj sea ms grande o pequea. Juega con varios valores de x y y, compilando y ejecutando cada vez para comprobar tus resultados. Nota que en el primer ejercicio slo debes modicar el archivo ClaseReloj.java, y en el segundo ejercicio slo debes modicar UsoReloj.java. No es necesario que toques, y ni siquiera que mires VistaRelojAnalogico.java. Los interiores de VistaRelojAnalogico.java los veremos ms adelante, cuando hayamos manejado ya interfaces grcas.

20

Preguntas
1. Entiendes cmo funciona el mtodo avanzaHora? Justica tu respuesta. 2. Entiendes cmo funciona el mtodo ponTiempo? Justica tu respuesta.

Prctica: Variables, tipos y operadores

The subtlest bugs cause the greatest damage and problems. Corollary - A subtle bug will modify storage thereby masquerading as some other problem. Murphys Laws of Computer Programming #3

Meta
Que el alumno aprenda a utilizar los tipos bsicos de Java y sus operadores, y que entienda el concepto de referencia.

Objetivos
Al nalizar la prctica el alumno ser capaz de:

22 utilizar la clase Consola para hacer salida en pantalla; manejar variables locales, tipos bsicos y literales de Java; entender lo que son referencias y manejar operadores y expresiones.

Desarrollo
Un programa (en Java o cualquier lenguaje de programacin) est escrito para resolver algn problema, realizando una o ms tareas. En el caso particular de Java, se utiliza la orientacin a objetos para resolver estos problemas. Utilizar la orientacin a objetos quiere decir, entre otras cosas, abstraer el mundo en objetos y a estos objetos agruparlos en clases. En la prctica anterior, por ejemplo, utilizamos las clases ClaseReloj y VistaRelogAnalogico, que abstraan el funcionamiento de un reloj y su representacin externa al implementar las interfaces Reloj y VistaReloj. Vamos a comenzar a resolver problemas utilizando orientacin a objetos. Esto quedar dividido en dos partes fundamentales: la primera ser identicar los objetos que representan nuestro problema, agruparlos en clases y denir precisamente el comportamiento (las funciones) que tendrn para que resuelvan nuestro problema. La segunda parte es un poco ms detallada. Una vez denido el comportamiento de una clase, hay que sentarse a escribirlo. Esto se traduce en implementar los mtodos de la clase. Para implementarlos, es necesario manejar las partes bsicas o atmicas del lenguaje: las variables, sus tipos, y sus operadores. En esta prctica jugaremos con esta parte fundamental del lenguaje Java; en la siguiente utilizaremos lo que aprendamos aqu para comenzar a escribir nuestras clases.

Una clase de prueba


Para jugar con variables, vamos a necesitar un bloque de ejecucin donde ponerlas, o sea un mtodo, y ya que necesitamos un mtodo, vamos a necesitar tambin una clase donde ponerlo. Un problema es que no sabemos hacer todava mtodos, as que utilizaremos el mtodo main, que ya utilizamos en las prcticas pasadas. Escribamos pues una clase llamada Prueba, y utilicemos el mtodo main de ella. Nuestra clase Prueba quedara como sigue:

Variables, tipos y operadores

23

1 2 3 4 5 6 7 8

package i c c 1 . p r a c t i c a 3 ; public class Prueba { public s t a t i c void main ( S t r i n g [ ] args ) { } }

An no hemos visto paquetes, as que por ahora no explicaremos qu signica la lnea


1 package i c c 1 . p r a c t i c a 3 ;

Por ahora slo necesitamos saber que eso signica que el archivo Prueba.java debe estar en un directorio llamado practica3, y que ste a su vez debe estar en un directorio llamado icc1.
Actividad 3.1 Baja el archivo practica3.tar.gz, y descomprmelo como lo hemos hecho en prcticas pasadas. El archivo contiene ya un build.xml para esta prctica, y la estructura de directorios necesaria (o sea src/icc1/practica3). Dentro de ese directorio crea tu archivo Prueba.java para tu clase Prueba. Si ests usando XEmacs (y ste est bien congurado), y pones esto: en la primera lnea, cada vez que abras el archivo, XEmacs har disponibles varias funcionalidades especiales para editar archivos de Java. Entre otras cosas, colorear de forma especial la sintaxis del programa, y te permitir llamar a Ant con C-c C-v C-b .
/* * jde * */

Como recordars de la prctica anterior, utilizamos el directorio src para poner nuestro cdigo fuente (y as tambin lo especiacmos en el build.xml). Y como nuestro paquete es icc1.practica3, dentro de src creamos el directorio icc1, y dentro de ste otro llamado practica3. Con el build.xml proporcionado podemos compilar y ejecutar el programa como venamos hacindolo hasta ahora. Podemos comprobar (viendo el archivo build.xml) que ahora la clase que ejecutamos en el objetivo run es icc1.practica3.Prueba. Esto es la clase Prueba del paquete icc1.practica3. Varios deben estar ahora hacindose algunas preguntas: Qu quiere decir exactamente public class Prueba? Para qu es la palabra static ? De dnde sali el constructor de Prueba?

24 Todas esas preguntas las vamos a contestar en la siguiente prctica; en sta nos vamos a concentrar en variables, tipos y operadores. La clase Prueba se ve un poco vaca; sin embargo, es una clase completamente funcional y puede ser compilada y ejecutada.
Actividad 3.2 Compila y ejecuta la clase (desde el directorio donde est el archivo build.xml): # ant compile # ant run No va a pasar nada interesante (de hecho no va a pasar nada), pero con esto puedes comprobar que es una clase vlida, pues compila bien y no termina con un mensaje de rror al ejecutarse.

Ahora, queremos jugar con variables, pero no va a servir de mucho si no vemos los resultados de lo que estamos haciendo. Para poder ver los resultados de lo que hagamos, vamos a utilizar a la clase Consola.

La clase Consola
Para tener acceso a la pantalla y ver los resultados de lo que le hagamos a nuestras variables, vamos a utilizar la clase Consola. Esta clase nos permitir, entre otras cosas, escribir en la pantalla.
Actividad 3.3 De la pgina referida al nal de las prcticas, baja el archivo icc1.jar, y ponlo en el directorio de la prctica 3.

Cmo vamos a utilizar la clase Consola dentro de nuestra clase Prueba? Primero, debemos decirle a nuestra clase acerca de la clase Consola. Esto lo logramos escribiendo
import i c c 1 . i n t e r f a z . Consola ;

antes de la declaracin de nuestra clase. Veremos ms detalladamente el signicado de import cuando veamos paquetes. En segundo lugar, cuando compilemos tambin tenemos que decirle al compilador dnde buscar a la clase Consola. Para poder hacer esto, necesitamos modicar nuestro build.xml.

Variables, tipos y operadores Actividad 3.4 Modica el archivo build.xml cambiando la lnea:
6 7 < j a v a c s r c d i r ="src" d e s t d i r ="build" debug="true" d e b u g l e v e l ="source" / >

25

por
6 7 < j a v a c s r c d i r ="src" d e s t d i r ="build" debug="true" d e b u g l e v e l ="source" c l a s s p a t h ="icc1.jar" / >

El classpath es donde Java (el compilador y la mquina virtual) buscan clases extras. En ltimo lugar, cuando ejecutemos de nuevo tenemos que decirle a la JVM dnde est nuestra clase Consola; para esto tambin modicamos nuestro build.xml.

Actividad 3.5 Modica el archivo build.xml cambiando el objetivo run de la siguiente manera:
10 11 12 13 14 15 16 17 < t a r g e t name="run" depends="compile"> < j a v a classname="icc1.practica3.Prueba" f o r k ="true"> <classpath> <pathelement path="build" / > <pathelement l o c a t i o n ="icc1.jar" / > < / classpath> < / java> </ target>

De la misma manera, estamos aadiendo el archivo icc1.jar al classpath para que la JVM lo encuentre al ejecutar el programa. Qu podemos hacer con la clase Consola? De hecho, podemos hacer muchas cosas, que iremos descubriendo poco a poco conforme avancemos en el curso; pero por ahora, nos conformaremos con utilizarla para mostrar los valores de nuestras variables.

Actividad 3.6 Con la ayuda de un navegador, ve la documentacin generada de la clase Consola. Se irn explicando y utilizando todos los mtodos de la clase conforme se avance en el material.

26 Para mostrar informacin con la consola, necesitamos crear un objeto de la clase consola y con l llamar a los mtodos imprime o imprimeln. Por ejemplo, modica el mtodo main de la clase Prueba de la siguiente manera:1
1 2 3 4 5 6 7 8 9 10 11 12 package i c c 1 . p r a c t i c a 3 ; import i c c 1 . i n t e r f a z . Consola ; public class Prueba { public s t a t i c void main ( S t r i n g [ ] args ) { Consola c ; c = new Consola ( "Valores de variables" ) ; c . imprime ( "Hola mundo." ) ; }

Compila y ejecuta la clase. El resultado lo puedes ver en la gura 3.1.


Figura 3.1 Consola

Para terminar el programa, cierra la ventana de la consola. Analicemos las tres lneas que aadimos a la funcin main:
8
1

Consola c ; para representar los espacios dentro de las cadenas.

Observa que estamos utilizando

Variables, tipos y operadores

27

En esta lnea slo declaramos un nuevo objeto de la clase Consola llamado c.


9 c = new Consola ( "Valores de variables" ) ;

En esta lnea construimos un objeto de la clase Consola y lo asociamos a c. El constructor que usamos recibe como parmetro una cadena, que es el ttulo de la ventana de nuestra consola. Al construir el objeto, la ventana con nuestra consola se abre automticamente.
10 c . imprime ( "Hola mundo." ) ;

El mtodo imprime de la clase Consola hace lo que su nombre indica; imprime en la consola la cadena que recibe como parmetro, que en nuestro ejemplo es "Hola mundo.". Dijimos que podamos utilizar los mtodos imprime e imprimeln para mostrar informacin en la consola. Para ver la diferencia, imprime otra cadena en la consola:
9 10 11 12 Consola c ; c = new Consola ( "Valores de variables" ) ; c . imprime ( "Hola mundo." ) ; c . imprime ( "Adis mundo." ) ;

Compila y ejecuta la clase. El resultado lo puedes ver en la gura 3.2.


Figura 3.2 Resultado de imprime en la consola

28 Ahora, utilicemos el mtodo imprimeln en lugar de imprime:


13 14 c . i m p r i m e l n ( "Hola mundo." ) ; c . i m p r i m e l n ( "Adis mundo." ) ;

Si compilas y ejecutas la clase, vers algo parecido a la gura 3.3.


Figura 3.3 Resultado de imprimeln en la consola

La diferencia entre imprime e imprimeln es que la segunda imprime un salto de lnea despus de haber impreso la cadena que le pasamos como parmetro. El salto de lnea en Java est representado por el carcter \n. Otra forma de conseguir el mismo resultado es hacer
13 14 c . imprime ( "Hola mundo.\n" ) ; c . imprime ( "Adis mundo.\n" ) ;

o incluso
13 c . imprime ( "Hola mundo.\nAdis mundo.\n" ) ;

La clase Consola ofrece muchos mtodos, que iremos utilizando a lo largo de las dems prcticas. En sta slo veremos imprime e imprimeln.

Variables, tipos y operadores Actividad 3.7 Crea la clase Prueba en el archivo Prueba.java y realiza (valga la redundancia) pruebas con imprime e imprimeln.

29

Variables locales, tipos bsicos y literales


Ya hemos trabajado con variables locales. En la prctica pasada, reloj y vista eran variables locales del mtodo main en la clase UsoReloj. En esta prctica, c es un variable local de la funcin main en la clase Prueba. Las variables locales se llaman as porque slo son visibles localmente. Cuando escribamos funciones, no van a poder ver (y por lo tanto no podrn usar) a c porque esta variable slo puede ser vista dentro de la funcin main. Se conoce como el alcance de una variable a los lugares dentro de un programa desde donde puede ser vista la variable. Recordemos la declaracin de reloj y vista de la prctica anterior:
Reloj r e l o j ; ... VistaReloj vista ;

Las dos son variables locales, pero tienen una diferencia muy importante: son de tipos diferentes. Java es un lenguaje de programacin fuertemente tipicado, lo que signica que una variable tiene un nico tipo durante toda su vida, y que por lo tanto nunca puede cambiar de tipo. Esto quiere decir que lo siguiente sera invlido:
v i s t a = new C l a s e R e l o j ( ) ;

Si intentan compilar algo as, javac se va a negar a compilar la clase; les va a decir que vista no es de tipo ClaseReloj. Vamos a empezar a declarar variables. Para declarar una variable, tenemos que poner el tipo de la variable, seguido del nombre de la variable:
int a;

Si queremos usar varias variables de un solo tipo, podemos declararlas todas juntas:
int a , b , c , d;

30

Con eso declaramos una variable de cierto tipo, pero, qu son los tipos? Para empezar a ver los tipos de Java, declaremos un entero llamado a en el mtodo main.
7 8 9 10 11 12 13 public s t a t i c void main ( S t r i n g [ ] args ) { Consola c ; c = new Consola ( "Valores de variables" ) ; / / Declaramos un e n t e r o " a " ; int a;

La variable a es de tipo entero ( int ). Java tiene algunos tipos especiales que se llaman tipos bsicos. Los tipos bsicos dieren de todos los otros tipos en que NO son objetos; no tienen mtodos ni variables de clase, y no tienen constructores. Para inicializar (que no es igual que construir) una variable de tipo bsico, sencillamente se le asigna el valor correspondiente, por ejemplo:
a = 5;

Java tiene ocho tipos bsicos, que puedes consultar en la tabla 3.1.
Tabla 3.1 Tipos bsicos de Java

Nombre
byte short int long oat double char boolean

Descripcin Enteros con signo de 8 bits en complemento a dos Enteros con signo de 16 bits en complemento a dos Enteros con signo de 32 bits en complemento a dos Enteros con signo de 64 bits en complemento a dos Punto otante de 32 bits de precisin simple Punto otante de 64 bits de precisin doble Carcter de 16 bits Valor booleano (verdadero o falso)

En el ejemplo de arriba, qu representa el 5? El 5 representa una literal, en este caso de tipo entero. Las literales son valores que no estn atados a una variable. Todos los tipos bsicos tienen literales:
int double char boolean entero ; doble ; caracter ; booleano ; Contina en la siguiente pgina

Variables, tipos y operadores

31
Contina de la pgina anterior

entero doble caracter booleano

= = = =

1234; 13.745; a ; true ;

En general, a cualquier serie de nmeros sin punto decimal Java lo considera una literal de tipo int , y a cualquier serie de nmeros con punto decimal Java lo considera una literal de tipo double. Si se quiere una literal de tipo long se tiene que poner una L al nal, como en 1234L, y si se quiere un otante hay que poner una F al nal, como en 13.745F. No hay literales explcitas para byte ni para short. Las nicas literales de tipo boolean son true y false. Las literales de carcter son a, Z, +, etc. Para los carcteres como el salto de lnea o el tabulador, las literales son especiales, como \n o \t. Si se necesita la literal del carcter \, se utiliza \\. Si se necesita un carcter internacional, se puede utilizar su cdigo Unicode2 , por ejemplo, \u0041 representa el carcter A (el entero que sigue a \u est escrito en hexadecimal, y deben escribirse los cuatro dgitos). La fuerte tipicacin de Java se aplica tambin a sus tipos bsicos:
i n t a = 13; / / C d i g o v l i d o i n t b = t r u e ; / / C d i g o i n v l i d o !

Sin embargo, el siguiente es cdigo vlido tambin:


f l o a t x = 12; byte b = 5 ; int a = b;

Si somos precisos, la literal 12 es de tipo int (como dijimos arriba); y sin embargo se lo asignamos a una variable de tipo oat . De igual forma, b es de tipo byte, pero le asignamos su valor a a, una variable de tipo int . Dejaremos la explicacin formal de por qu esto no viola la fuerte tipicacin hasta que veamos conversin explcita de datos; por ahora, basta con entender que un entero con signo que cabe en 8 bits (los de tipo byte), no tiene ninguna dicultad hacer que quepa en un entero con signo de 32 bits, y que todo entero con signo que quepa en 32 bits, puede ser representado por un punto otante de 32 bits. Por la misma razn, el siguiente cdigo s es invlido:
Unicode es un cdigo de carcteres pensado para reemplazar a ASCII. ASCII slo tena (originalmente) 127 caracteres y despus fue extendido a 255. Eso basta para el alfabeto latino, con acentos incluidos, y algunos carcteres griegos; pero es completamente intil para lenguas como la china o japonesa. Unicode tiene capacidad para 65,535 caracteres, lo que es suciente para manejar estos lenguajes. Java es el primer lenguaje de programacin en soportar nativamente Unicode.
2

32
int a = 1.2; int b = 5; byte c = b ;

El primero debe ser obvio; no podemos hacer que un punto otante sea representado por un entero. Sin embargo, por qu no podemos asignarle a c el valor b, cuando 5 s cabe en un entero de 8 bits con signo? No podemos porque al compilador de Java no le interesa qu valor en particular tenga la variable b. Lo nico que le importa es que es de tipo int , y un int es muy probable que no quepa en un byte. El compilador de Java juega siempre a la segura.

Actividad 3.8 Prueba los pequeos ejemplos que se han visto, tratando de compilar todos (utiliza el mtodo main para tus ejemplos). Ve qu errores manda el compilador en cada caso, si es que manda.

Cmo vamos a imprimir en nuestra consola nuestros tipos bsicos? Pues igual que nuestras cadenas:
int a = 5; c . imprimeln ( a ) ;

Los mtodos imprime e imprimeln de la clase Consola soportan todos los tipos de Java. Una ltima observacin respecto a variables locales (sean stas de tipo bsico u objetos). Est claro que para usar una variable local en un mtodo, tiene que estar declarada antes de ser usada. Esto es obvio porque no podemos usar algo antes de tenerlo. Empero, hay otra restriccin: no podemos usar una variable antes de inicializarla; el siguiente cdigo no compila:
int a; i n t b = a ; / / No podemos usar l a v a r i a b l e a todava . . .

Estamos tratando de usar a sin que haya sido inicializada. Esto es invlido para el compilador de Java. Dijimos que esto se aplica a tipos bsicos y objetos, y ms arriba dijimos que inicializar no era lo mismo que construir. Esto es porque tambin podemos inicializar variables que sean objetos sin necesariamente construir nada. Para entender esto, necesitamos comprender qu son las referencias.

Variables, tipos y operadores

33

Referencias
Cuando hacemos
Consola c ;

dijimos que estamos declarando un objeto de la clase Consola. As se acostumbra decir, y as lo diremos siempre a lo largo de estas prcticas. Sin embargo, formalmente hablando, estamos declarando una variable de tipo referencia a un objeto de la clase Consola. Una referencia es una direccin en memoria. Al construir en c con new
c = new Consola ( ) ;

lo que hace new es pedirle a la JVM que asigne memoria para poder guardar en ella al nuevo objeto de la clase Consola. Entonces new regresa la direccin en memoria del nuevo objeto, y se guarda en la variable c. Esto es importante; las variables de referencia a algn objeto tienen valores que son direcciones de memoria, as como las variables de tipo entero tienen valores que son enteros en complemento a dos, o las variables de tipo booleano tienen valores que son true o false. Si despus de construir p hacemos
Consola c2 ; c2 = c ;

entonces ya no estamos construyendo a c2 (no se est asignando memoria a ningn nuevo objeto). Lo que estamos haciendo es inicializar a c2 con el valor de c, o sea, con la direccin de memoria a la que hace referencia c. Despus de eso, c y c2 son dos variables distintas, que tienen el mismo valor, o sea, que hacen referencia al mismo objeto3 . Todas las referencias (no importa de qu clase sean) tienen una nica literal, que es null. sta es una referencia que por denicin es invlida; no apunta nunca a ninguna direccin vlida de la memoria. La referencia null es muy utilizada para varios propsitos, y en particular sirve para inicializar variables de objetos que an no deseamos construir. Veremos ms ejemplos con null en las siguientes prcticas. Una vez denidas las referencias, podemos dividir a las variables de Java en dos; toda variable de Java es de un tipo bsico, o alguna referencia. No hay otras. Adems de esto, todas las referencias apuntan a algn objeto, o tienen el valor null.
3

Se suele decir tambin que apuntan al mismo objeto.

34

Operadores
Sabemos ya declarar todas las posibles variables de Java. Una vez que tengamos variables, podemos guardar valores en ellas con el operador de asignacin =; pero adems debemos poder hacer cosas interesantes con los valores que puede tomar una variable. Para hacer cosas interesantes con nuestras variables, es necesario utilizar operadores. Los operadores de Java aparecen en la tabla 3.2.

Tabla 3.2 Operadores de Java.

Operandos posjo unario posjo unario posjo n-ario posjo unario posjo unario unario prejo unario prejo unario prejo unario prejo unario prejo unario prejo unario prejo unario prejo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo

Asocia tividad derecha derecha derecha izquierda izquierda derecha derecha izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda

Smbolo
[ ] . (<parmetros>) <variable>++ <variable> ++<variable> <variable> +<expresin> <expresin> <expresin> !<expresin> new <constructor> (<tipo>)<expresin> * / % + <<

Descripcin arreglos selector de clase lista de parmetros auto post-incremento auto post-decremento auto pre-incremento auto pre-decremento signo positivo signo negativo complemento en bits negacin constructor casting multiplicacin divisin mdulo suma resta corrimiento de bits a la izquierda corrimiento de bits a la derecha llenando con ceros corrimiento de bits a la derecha propagando signo relacional menor que relacional menor o igual que relacional mayor que
Contina en la siguiente pgina

izquierda >> izquierda >>> izquierda < izquierda <= izquierda >

Variables, tipos y operadores Tabla 3.2 Operadores de Java

35

Contina de la pgina anterior

Operandos binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo ternario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo binario injo

Asocia tividad izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda izquierda derecha derecha derecha derecha derecha derecha derecha derecha derecha derecha derecha derecha

Smbolo
>= instanceof == != & ^ | && || <exp log > ? <exp> : <exp> = += = *= /= %= >>= <<= >>>= &= ^= |=

Descripcin relacional mayor o igual que relacional ejemplar de relacional, igual a relacional, distinto de AND de bits XOR de bits OR de bits AND lgico OR lgico Condicional aritmtica asignacin autosuma y asignacin autoresta y asignacin autoproducto y asignacin autodivisin y asignacin automdulo y asignacin autocorrimiento derecho y asignacin autocorrimiento izquierdo y asignacin autocorrimiento derecho con propagacin y asignacin auto-AND de bits y asignacin auto-XOR de bits y asignacin auto-OR de bits y asignacin

Los operadores en general se comportan como uno esperara; si uno hace


int a = 3 + 2;

entonces a toma el valor 5. Todos los operadores tienen un dominio especco; cosas como
true + false ;

36

no tienen sentido. Cuando se opera con tipos distintos, pero compatibles, se asume el tipo ms general. Por ejemplo, si tenemos
int a = 2; float x = 1.5;

entonces a+x es de tipo oat . Los operadores funcionan de acuerdo al tipo de los operandos. Si se dividen dos enteros con el operador / , se hace divisin entera; el resultado se trunca al mayor entero menor o igual al resultado de la divisin. Si se dividen otantes o dobles, el resultado es la divisin real (o lo ms cercano, de acuerdo a la precisin simple o doble de cada tipo). Hay varios operadores en la tabla que funcionan sobre bits. Esto quiere decir que operan sobre un valor de tipo entero, pero jndose en los bits, ms que en el valor en s. Veamos un ejemplo.
i n t a = 13; i n t b = 19; int c = a | b;

La variable a vale 13 y la b vale 19. En bits esto es a = 00000000000000000000000000001101 b = 00000000000000000000000000010011 (recordemos que los enteros tienen 32 bits). Para no tener que escribir tanto, usamos hexadecimal para representar los valores y podemos verlos como 0x0000000D y 0x00000013. Cul es valor de la variable c entonces? Estamos usando el OR para bits; se aplica un OR a cada uno de los bits del entero, y el resultado es c = 00000000000000000000000000011111 o bien 0x0000001F, que a su vez es el valor 31 en base 10. Siempre hay que tener en cuenta que los tipos enteros de Java (byte, short, int , long) tienen signo; eso quiere decir que el bit ms signicativo en estos tipos guarda el signo. La tabla 3.2 est ordenada en orden de precedencia. La precedencia de los operadores determina el orden en que sern aplicados. Por ejemplo en

Variables, tipos y operadores


int a = 3 + 2 * 5;

37

el valor de a es 13, no 25, ya que la precedencia de la multiplicacin es mayor que la de la suma. Si queremos que la suma se aplique antes, tenemos que hacer
i n t a = (3 + 2) * 5;

Pueden utilizar la tabla de operadores para saber qu operador tiene precedencia sobre cules; sin embargo, en general es bastante intuitivo. En caso de duda, poner parntesis nunca hace dao (pero hace que el cdigo se vea horrible; los parntesis son malosMR ).4 Los operadores relacionales (<, >, <=, >=) funcionan sobre tipos numricos, y regresan un valor booleano. La igualdad y la desigualdad (==, !=) funcionan sobre todos los tipos de Java. En el caso de las referencias, == regresar true si y slo si las referencias apuntan a la misma posicin en la memoria; al mismo objeto. Hay que notar que la asignacin (=) es muy diferente a la igualdad (==). La asignacin toma el valor de la derecha (se hacen todos los cmputos que sean necesarios para obtener ese valor), y lo asigna en la variable de la izquierda. El valor debe ser de un tipo compatible al de la variable. Tambin hay algunos operadores de conveniencia, los llamados auto operadores. Son +=, =, *=, /=, %=, >>=, <<=, >>>=, &=, ^=, y |=. La idea es que si tenemos una variable entera llamada a, entonces
a += 1 0 ;

es exactamente igual a
a = a + 10;

E igual con cada uno de los auto operadores. Como el caso de sumarle (o restarle) un uno a una variable es muy comn en muchos algoritmos, los operadores ++ y hacen exactamente eso. Pero hay dos maneras de aplicarlos: prejo y post jo. Si tenemos
int a = 5; i n t b = a ++;

entonces a termina valiendo 6, pero b termina valiendo 5. Esto es porque el operador regresa el valor de la variable antes de sumarle un uno, y hasta despus le suma el uno a la variable. En cambio en
4

Parenthesis are evilTM .

38
int a = 5; i n t b = ++a ;

las dos variables terminan valiendo 6, porque el operador primero le suma un uno a a, y despus regresa el valor de la variable. Los operadores lgicos && (AND) y || (OR) funcionan como se espera que funcionen (p q es verdadero si y slo si p y q son verdaderos, y p q es verdadero si y slo si p es verdadero o q es verdadero), con una pequea diferencia: funcionan en corto circuito. Esto quiere decir que si hacemos
i f ( a < b && b < c )

y a < b es falso, entonces el && ya no comprueba su segundo operando. Ya sabe que el resultado va a dar falso, as que ya no se molesta en hacer los clculos necesarios para ver si b < c es verdadero o falso. Lo mismo pasa si el primer operando de un || resulta verdadero. Es importante tener en cuenta esto, porque aunque en lgica matemtica nos dijeron que p q q p, a la hora de programar esto no es 100 % cierto. Un operador bastante til es la condicional aritmtica (?:). Funciona de la siguiente manera:
int z = (a < b) ? 1 : 0;

Si a es menor que b, entonces el valor de z ser 1. Si no, ser 0. La condicional aritmtica funciona como un if chiquito, y es muy til y compacta. Por ejemplo, para obtener el valor absoluto de una variable (digamos a) slo necesitamos hacer
i n t b = ( a < 0 ) ? a : a ;

Hay varios operadores que funcionan con referencias. Algunos funcionan con referencias y con tipos bsicos al mismo tiempo (como = y ==, por ejemplo); pero otros son exclusivos de las referencias. Los operadores ms importantes que tienen las referencias son new, que las inicializa, y . (punto), que nos permite interactuar con las partes de un objeto. Hay otros operadores para los objetos, y otros para tipos bsicos, que iremos estudiando en las prcticas que siguen.

Expresiones
Una vez que tenemos variables (tipos bsicos u objetos), literales y operadores, obtenemos el siguiente nivel en las construcciones de Java. Al combinar una o ms variables o literales con un operador, tenemos una expresin.

Variables, tipos y operadores

39

Las expresiones no son atmicas; pueden partirse hasta que lleguemos a variables o literales y operadores. Podemos construir expresiones muy rpido:
a ++; // (a++); // (a++)*3; // 5 + ( ( a + + ) * 3 / 2 + 5 / r e l o j . dameMinuto ( ) * r e l o j // Esto es una e x p r e s i n . Esto tambin . Tambin . . dameHoras ( ) ) ; S , tambin .

En general, todas las expresiones regresan un valor. No siempre es as, sin embargo, como veremos en la siguiente prctica. Podemos hacer expresiones tan complejas como queramos (como muestra el ltimo de nuestros ejemplos arriba), siempre y cuando los tipos de cada una de sus partes sean compatibles. Si no lo son, el compilador de Java se va a negar a compilar esa clase. El siguiente nivel en las construcciones de Java lo mencionamos en la prctica pasada: son los bloques. En la prxima prctica analizaremos nivel de construccin ms importante de Java: las clases.

Ejercicios
Todas estas pruebas las tienes que realizar dentro del mtodo main de tu clase Prueba. 1. Trata de obtener el mdulo5 ( %) de un otante. 2. Declara un oat llamado x, y asgnale la literal 1F. Declara un oat llamado y y asgnale la literal 0.00000001F. Declara un tercer oat llamado z y asgnale el valor que resulta de restarle y a x. Imprime z en tu consola. 3. Imprime el valor de la siguiente expresin: 1>>1. Ahora imprime el valor de la siguiente expresin 1>>1. 4. Declara dos variables booleanas p y q. Asgnales a cada una un valor (true o false, t decide), e imprime el valor de la expresin (p q) en sintaxis de Java. Despus imprime el valor de la expresin p q utilizando la consola.

Cambia los valores de p y q para hacer las cuatro combinaciones posibles, imprimiendo siempre el resultado de las dos expresiones. Con esto demostrars la regla de De Morgan caso por caso.
El resultado que se obtiene de la expresin a % b, con a y b enteros, es el residuo entero lo que sobra que resulta al dividir a entre b.
5

40

Preguntas
1. Crees que sea posible asignar el valor de un otante a un entero? Cmo crees que funcionara? 2. Observa el siguiente cdigo
int a = 1; int b = 2; int c = 3; ( a > 3 && ++a <= 2 ) ? b++ : c;

Sin compilarlo, cul es el valor nal de a, b, c? Compila y compara lo que pensaste con el resultado real. Explica porqu cada variable termina con el valor que termina.

Prctica: Interfaces y clases por dentro

A debugged program that crashes will wipe out source les on storage devices when there is the least available backup. Murphys Laws of Computer Programming #4

Meta
Que el alumno aprenda cmo escribir clases.

Objetivos
Al nalizar la prctica el alumno ser capaz de: entender cmo est formada una clase;

42 manejar cadenas; y escribir clases y clases de uso.

Desarrollo
En la prctica anterior dijimos que usar el paradigma orientado a objetos consista en abstraer en objetos el problema que queremos resolver. Abstraer en objetos signica identicar los elementos principales de nuestro problema, crear una clase que abarquea todos sus ejemplares, y escribir los mtodos necesarios de cada clase para que resuelvan nuestro problema. Por qu decimos crear una clase que abarque todos sus ejemplares? Un problema, en ciencias de la computacin, abarca muchos posibles (generalmente una innidad) de casos concretos. No escribimos una solucin para sumar 3 y 5; escribimos una solucin para sumar dos nmeros a y b, para todos los posibles valores de a y b1 . sa siempre ha sido la idea de las ciencias de la computacin, desde el inicio cuando las computadoras utilizaban bulbos, y los programas se guardaban en tarjetas perforadas. La diferencia es cmo escribimos esta solucin que abarque tantos casos como podamos. Hace algunos aos se ide el paradigma orientado a objetos como un mtodo para escribir las soluciones a nuestros problemas. No es el nico paradigma; pero muchos pensamos que es el mejor y ms general. La idea es que tomemos nuestro problema y denamos los componentes que lo forman. Cada uno de estos componentes ser un objeto de nuestro problema. Pero no escribimos un solo objeto. Escribimos una clase que abarca a todas las encarnaciones posibles de nuestros objetos. Esto tiene muchsimas ventajas: Cada componente es una entidad independiente. Esto quiere decir que podemos modicar cada uno de ellos (hacerlo ms rpido, ms entendible, que abarque ms casos), y los dems componentes no tendrn que modicarse para ello. A esto se le llama encapsulamiento. Cuando resolvamos otro problema distinto, es posible que al denir sus componentes descubramos que uno de ellos ya lo habamos denido para un problema anterior. Entonces slo utilizamos esa clase que ya habamos escrito. A esto se le llama reutilizacin de cdigo. Tenemos un ejemplo de eso ya en las manos. La clase Consola fue escrita para resolver el problema de cmo mostrar informacin al usuario (tambin de cmo obtener informacin del usuario; veremos eso ms adelante en esta prctica). El
A veces, sin embargo, resolver todos los casos es demasiado costoso, y restringimos el dominio de nuestro problema. Sin embargo, nunca escribimos una solucin que slo sirva con uno o dos casos concretos; siempre se trata de abarcar el mayor nmero de casos posibles.
1

Interfaces y clases por dentro

43

problema ya est resuelto; la clase Consola ya est escrita, y ahora la utilizaremos casi todo el curso, sin necesidad de modicarla o escribirla toda cada vez que queramos mostrar informacin al usuario. Hay muchas ms ventajas; pero para entenderlas necesitamos estudiar ms conceptos. La ventaja ms importante, sin embargo, es que nuestro problema puede quedar formalmente dividido en una serie de componentes bien denidos. Con esto dividimos al problema en problemas ms pequeos. Divide y vencers.

Clases e interfaces de Java


Venimos hablando de clases desde la primera prctica, y an no hemos dicho bien qu es una clase. Una clase es un prototipo o plantilla que dene los mtodos y variables que son comunes a todos los objetos de cierto tipo. En la clase denimos el comportamiento de todos los objetos posibles de esa clase. En Java las clases estn compuestas de mtodos y variables de clase. Las variables de clase no son iguales a las variables locales; tienen varias diferencias que iremos viendo a lo largo de esta prctica. Las clases se declaran en Java como se muestra a continuacin:
class <NombreDeClase> {

<variableDeClase1 > <variableDeClase2 > ... <variableDeClaseM > <m todo1 > <m todo2 > ... <m todoN > }

(Ya sabemos que todo esto debe estar en un archivo que se llame como la clase, o sea <NombreDeClase>.java). Pusimos todas las variables de clase juntas al inicio y todos los mtodos juntos al nal; pero realmente pueden ir en el orden que se quiera. Sin embargo, en estas prcticas ser requisito que las variables de clase estn todas juntas al inicio. Las interfaces son muy parecidas a las clases, pero en lugar de class utilizan interface, sus mtodos son vacos (no tienen cuerpo), y no declaran variables de clase:

44

i n t e r f a c e <NombreDeInterfaz > <m todoVac o1> <m todoVac o2> ... <m todoVac oN> }

Esto debe estar en un archivo llamado <NombreDeInterfaz>.java. Para aterrizar todo lo que vayamos discutiendo, iremos construyendo una interfaz para representar matrices cuadradas en general, que llamaremos MatrizCuadrada, y una clase para representar matrices de dos renglones por dos columnas que llamaremos (contrario a todo lo que podra pensarse) Matriz2x2, que implementar a MatrizCuadrada. La interfaz es muy sencilla:
1 2 3 4 5 6 7 8 9 10 11 12 13 public i n t e r f a c e MatrizCuadrada { public MatrizCuadrada suma ( MatrizCuadrada m) ; public MatrizCuadrada r e s t a ( MatrizCuadrada m) ; public MatrizCuadrada m u l t i p l i c a ( MatrizCuadrada m) ; public MatrizCuadrada m u l t i p l i c a ( double x ) ; public double g e t D e t e r m i n a n t e ( ) ; }

Un poco ms adelante veremos porqu la denicin incluye public, porqu hay dos mtodos multiplica, y porqu el ltimo mtodo se llama getDeterminante y no dameDeterminante. Lo que tenemos aqu es, como hemos venido diciendo, un contrato. Cualquier clase que implemente a MatrizCuadrada est obligada a implementar los mtodos suma, resta, multiplica (los dos) y getDeterminante. Declararemos nuestra clase Matriz2x2 de la siguiente manera:
1 2 3 class M a t r i z 2 x 2 implements MatrizCuadrada { }

No sabemos todava declarar mtodos ni variables de clase, por lo que la dejaremos as por el momento. La clase no puede compilar (intntenlo), porque no implementa los mtodos que declara MatrizCuadrada.

Interfaces y clases por dentro

45

Actividad 4.1 Comienza a escribir la interfaz MatrizCuadrada y la clase Matriz2x2. Conforme se vayan aadiendo partes a lo largo de la prctica, velas agregando.

Antes de seguir con la clase Matriz2x2, veamos las partes de una clase en Java en general.

Variables de clase
Las variables de clase se declaran de forma casi idntica a las variables locales:
class AlgunaClase { int a; }

Pueden utilizarse todos los tipos de Java (referencias incluidas), y son variables que se comportan exactamente igual que sus equivalentes locales. Lo que distingue a una variable de clase de una variable local (tcnicamente), es su alcance y su vida. El alcance de una variable de clase es toda la clase. La variable puede ser vista en casi todos los mtodos de la clase (en un momento veremos en cules no), y utilizada o modicada dentro de todos ellos. La vida de una variable es el tiempo en que la variable existe. En el caso de las variables locales, viven (o existen) desde el momento en que son declaradas, hasta que el mtodo donde estn termine. Las variables de clase viven desde que el objeto es creado con new hasta que el objeto deja de existir. Esto es importante; una variable de clase se crea al mismo tiempo que el objeto al que pertenece, y sigue siendo la misma variable aunque sean llamados varios mtodos por el objeto. En cambio, una variable local existe dentro del mtodo y es distinta para cada llamada del mtodo. Cada vez que sea llamado un mtodo, todas las variables locales declaradas dentro de l son creadas de nuevo. No son las mismas a travs de distintas llamadas al mtodo. Aunque tcnicamente hay pocas diferencias entre una variable local y una variable de clase, conceptualmente la diferencia es enorme en orientacin a objetos. Las variables locales slo sirven para hacer cuentas, u operaciones en general. Las variables de clase en cambio son fundamentales; son las que guardan el estado de un objeto, las que proyectan el componente del mundo real al objeto con el que queremos representarlo en nuestro programa.

46 Vamos a aadirle variables de clase a nuestra clase Matriz2x2. Supongamos que los componentes de nuestra matriz estn dispuestos de la siguiente manera:

a b c d

Entonces nuestra clase necesita cuatro valores en punto otante para ser representada. Utilicemos el tipo double, para tener ms precisin en nuestras operaciones. Y slo para hacerlo ms interesante, aadamos otro doble para que represente el determinante de nuestra matriz.
1 2 3 4 5 6 7 class M a t r i z 2 x 2 implements MatrizCuadrada { double a ; double b ; double c ; double d ; double d e t e r m i n a n t e ; }

Podramos declararlas todas en una sola lnea con double a,b,c,d,determinante; pero acostumbraremos declarar as las variables de clase, una por rengln. Las variables de clase son nicas para cada objeto. Esto quiere decir que si tenemos dos objetos de nuestra clase Matriz2x2, digamos m1 y m2, entonces las variables a, b, c, d y determinante del objeto m1 pueden tener valores totalmente distintos a las variables correspondientes del objeto m2. Los estados de los objetos (en otras palabras, los valores de sus variables de clase) son independientes entre ellos. Si empezamos a modicar los valores de las variables del objeto m1, esto no afectar a las variables del objeto m2.

Mtodos
En los mtodos no pasa como con las variables, que hay locales y de clase. Todas las funciones (o mtodos, o procedimientos) son de clase. No hay mtodos locales. Ya hemos visto varios mtodos implementados (main principalmente), y tambin varios declarados en interfaces. Ahora vamos a explicar cmo se implementan y por qu se declaran como se declaran. Como nuestra clase Matrix2x2 implementa a la interfaz MatrizCuadrada, estamos obligados a implementar sus cinco mtodos. Comencemos con la multiplicacin con un escalar, que consiste en multiplicar a cada trmino de la matriz por el escalar:

Interfaces y clases por dentro

47

a b c d

xa xb xc xd

Un mtodo tiene un nombre, un tipo de regreso y parmetros. En Java se declaran as los mtodos:
<tipoDeRegreso > <nombreDelM todo > ( <par metro1 > , <par metro2 > , ... <par metroN >) { <cuerpoDelM todo > }

La nica diferencia (y muy importanta) entre declarar un mtodo en una interfaz y en una clase, es que en la interfaz no hay cuerpo para el mtodo; en otras palabras es:
<tipoDeRegreso > <nombreDelM todo > ( <par metro1 > , <par metro2 > , ... <par metroN > ) ;

En las interfaces, los mtodos nos dicen qu hace la interfaz. En las clases, los mtodos nos dicen qu hace la clase, y adems cmo lo hace. Cada parmetro es de la forma <tipo> <nombre>, donde el tipo es el tipo del parmetro, y el nombre es el nombre que tendr dentro de la funcin. A veces nuestros mtodos no van a hacer ningn clculo, sino que van a realizar acciones; en esos casos se pone que el tipo de regreso del mtodo es void. Las expresiones que consisten de un objeto llamando a un mtodo cuyo tipo de regreso es void no tienen valor. Son expresiones que no pueden usarse del lado derecho de una asignacin, o para pasarlas como parmetros a una funcin. Estamos implementando el mtodo multiplica de nuestra interfaz MatrizCuadrada, que recibe un doble (nuestro escalar), y que regresa una MatrizCuadrada. Noten que no regresa un objeto de la clase Matriz2x2; si lo hiciera, la rma del mtodo ya no sera igual a la de la interfaz, y nuestra clase no compilara (necesitamos implementar todos los mtodos declarados en la interfaz exactamente como estn en la interfaz):
5 6 7 public MatrizCuadrada m u l t i p l i c a ( double x ) { }

Lo nico que distingue este mtodo del de la interfaz es que tiene un bloque. El bloque est vaco, pero ya tiene un bloque.

48 Varios de ustedes deben haber observado algo raro. Vamos a multiplicar una matriz y un escalar; entonces por qu slo le pasamos un escalar a la funcin y no le pasamos una matriz tambin? La respuesta es que dentro de la funcin multiplica, ya hay una matriz; la que manda llamar el mtodo. Cuando queramos usar a la funcin multiplica, necesitaremos un objeto de la clase Matriz2x2 para que la llame. Supongamos que ya existe este objeto y que se llama m. Para llamar al mtodo, tendremos que hacer:
m. m u l t i p l i c a ( 2 . 5 ) ;

Entonces, dentro del mtodo tendremos el double x (con valor 2.5), y a m, que ya est implcitamente en el mtodo porque es el objeto que mand llamar a la funcin. Dentro del mtodo, m se va a llamar this; ste, en ingls, lo que hace nfasis en que ste mand llamar la funcin. La fuerte tipicacin de Java que vimos la prctica pasada se aplica tambin a los mtodos. El tipo de un mtodo est determinado por el tipo del valor que regresa, y el tipo de cada uno de sus parmetros. Por lo tanto, lo siguiente es ilegal:
m. m u l t i p l i c a ( t r u e ) ;

Sin embargo, todo esto s es legal:


double y = 3 . 7 ; float x = 23.14; short s = 5 ; m. m. m. m. m. multiplica multiplica multiplica multiplica multiplica (y ); (x ); (s ); (1.3); (4);

por lo que dijimos la prctica anterior de que hay tipos que s caben dentro de otros tipos. Lo siguiente en cambio es ilegal:
char c = m. m u l t i p l i c a ( 2 . 9 ) ;

El mtodo multiplica regresa un valor de tipo referencia a un objeto, cuya clase implementa la interfaz MatrizCuadrada. No podemos asignrselo a una variable de tipo char. Esto s es correcto:

Interfaces y clases por dentro

49

MatrizCuadrada m2; double w = 3 . 8 ; m2 = m. m u l t i p l i c a (w ) ;

y de hecho as ser usado casi siempre. En la variable m2 quedar guardada la matriz resultante de multiplicar m por la escalar 3.8 (cuando escribamos el cuerpo del mtodo). En los ejemplos vimos que le podemos pasar tanto literales como variables al mtodo. Esto es importante; las funciones en Java reciben sus parmetros por valor. Esto quiere decir que cuando la funcin es llamada, no importa qu haya dentro de los parntesis (una variable, una literal, cualquier expresin), el valor de lo que haya se copia, y se le asigna al parmetro dentro de la funcin (en nuestro caso, x). Por supuesto, ese valor debe ser de un tipo compatible al del parmetro. Dentro de la funcin, x se comporta de manera idntica a una variable local; pero se inicializa cuando se manda llamar el mtodo, con el valor que le pasamos. Lo mismo pasa con todos los parmetros de cualquier funcin. Supongamos que mandamos llamar a multiplica as:
double x = 1 4 . 7 5 ; m. m u l t i p l i c a ( x ) ;

y supongamos nuestro mtodo multiplica fuera as


5 6 7 8 public MatrizCuadrada m u l t i p l i c a ( double x ) { x = x / 5; return null ; }

La variable x que le pasamos al mtodo cuando lo llamamos, es totalmente independiente del parmetro x del mtodo. La variable x no se ve afectada cuando le hacemos x = x / 5 al parmetro x dentro del mtodo. La variable x seguir valiendo 14.75; Escribamos el cuerpo del mtodo multiplica
5 6 7 8 9 10 11 12 public MatrizCuadrada m u l t i p l i c a ( double x ) { double a , b , c , d ; a b c d } = = = = x x x x * * * * this . a ; this . b ; this . c ; this . d ;

50 Noten que declaramos cuatro variables locales que tienen el mismo tipo y se llaman igual que variables de clase existentes. No importa, porque usamos this para distinguir las variables de clase de las locales. Con esto, ya tenemos los cuatro componentes necesarios (a, b, c, d) para que hagamos una nueva matriz. El nico problema es que no sabemos hacer matrices. Para hacer una matriz, vamos a necesitar un constructor.

Constructores
Conceptualmente, los constructores sirven para que denamos el estado cero de nuestros objetos; el estado que tendrn al ser creados. Para motivos prcticos, los constructores son sencillamente mtodos que no tienen tipo de regreso, y que slo podemos llamar a travs del operador new. Como los mtodos, pueden recibir parmetros (por valor tambin), y puede hacerse dentro de ellos cualquier cosa que podamos hacer dentro de un mtodo, excepto regresar un valor. Un constructor obvio para nuestras matrices sera as:
5 6 7 8 9 10 11 12 M a t r i z 2 x 2 ( double a , double b , double c , double d ) { this . a = a ; this . b = b ; this . c = c ; this . d = d ; this . determinante = this . a * this . d this . b * this . c ;

(En tu archivo Matriz2x2.java, y en todas las clases que escribas, pon siempre los constructores antes que los mtodos). Observen que el constructor recibe parmetros que se llaman igual y que tienen el mismo tipo de las variables de clase. No importa, porque usamos this para tener acceso a las variables de clase, y as ya no hay confusin. En los constructores, this hace referencia al objeto que est siendo construido2 . En este caso el constructor inicializa las variables de clase con los valores que recibe, y calcula el determinante de la matriz que se obtiene de la siguiente manera: det
2

a b c d

= ad bc.

Con nuestra clase Matriz2x2 hemos estado usando mucho this para no confundirnos con los nombres de las variables. Pero cuando no haya conicto entre variables de clase y variables locales, podemos usar los nombres de las variables de clase sin el this y el compilador de Java sabr que hacemos referencia a las variables de clase.

Interfaces y clases por dentro

51

Ahora ya podemos terminar nuestro mtodo multiplica:


15 16 17 18 19 20 21 22 23 24 25 26 public MatrizCuadrada m u l t i p l i c a ( double x ) { double a , b , c , d ; a b c d = = = = x x x x * * * * this . a ; this . b ; this . c ; this . d ;

MatrizCuadrada r e s u l t a d o ; r e s u l t a d o = new M a t r i z 2 x 2 ( a , b , c , d ) ; return resultado ;

Nuestro mtodo multiplica ya est completo. Si se dan cuenta, las lneas


MatrizCuadrada r e s u l t a d o ; r e s u l t a d o = new M a t r i z 2 x 2 ( a , b , c , d ) ;

se parecen mucho a las lneas de la prctica 1:


Reloj r e l o j ; r e l o j = new C l a s e R e l o j ( ) ;

Podemos declarar a resultado como MatrizCuadrada porque Matriz2x2 implementa a MatrizCuadrada.

Polimorsmo
Tenemos que implementar el otro mtodo multiplica (est en nuestra interfaz). La multiplicacin de matrices cuadradas da como resultado una matriz cuadrada; en particular, multiplicar una matriz de dos por dos nos devuelve una matriz de dos por dos: a b c d x y z w ax + bz cx + dz ay + bw cy + dw

El mtodo va a recibir una matriz como parmetro (la otra matriz ser la que llame a la funcin), y va a regresar otra matriz (as est declarado). Ahora, el orden importa en la multiplicacin de matrices (no es conmutativa), y alguna de las matrices que vayamos a multiplicar tendr que llamar al mtodo, as que debemos denir cul ser; si el operando derecho o el izquierdo. Como va a ser usado as el mtodo:

52
MatrizCuadrada m1, m2; m1 = new M a t r i z 2 x 2 ( 1 , 2 , 3 , 4 ) ; m2 = new M a t r i z 2 x 2 ( 5 , 6 , 7 , 8 ) ; MatrizCuadrada m u l t ; m u l t = m1. m u l t i p l i c a (m2 ) ; / / M u l t i p l i c a m o s m1 por m2.

vamos a denir que la matriz que llame la funcin sea el operando izquierdo. Aqu algunos estarn exclamando (y desde la declaracin de la interfaz de hecho): hey, ya tenamos un mtodo que se llama multiplica!. Es verdad; pero en Java podemos repetir el nombre de un mtodo, siempre y cuando el nmero o tipo de los parmetros no sea el mismo (si no, vean en la documentacin generada de la clase Consola y busquen el mtodo imprime. . . mejor dicho los mtodos imprime). A esta caracterstica se le llama polimorsmo. Nuestro (segundo) mtodo multiplica quedara as:
30 31 32 33 34 35 36 37 38 39 40 41 42 public MatrizCuadrada m u l t i p l i c a ( MatrizCuadrada m) { double a , b , c , d ; M a t r i z 2 x 2 m2x2 = ( M a t r i z 2 x 2 )m; a b c d = = = = this . a this . a this . c this . c * * * * m2x2 . a m2x2 . b m2x2 . a m2x2 . b + + + + this . b this . b this . d this . d * * * * m2x2 . c ; m2x2 . d ; m2x2 . c ; m2x2 . d ;

MatrizCuadrada r e s u l t a d o ; r e s u l t a d o = new M a t r i z 2 x 2 ( a , b , c , d ) ; return resultado ;

Aqu hay una lnea que debe ser desconocida:


M a t r i z 2 x 2 m2x2 = ( M a t r i z 2 x 2 )m;

Por qu estamos haciendo eso? El mtodo recibe una MatrizCuadrada (tiene que ser as porque as se declar en la interfaz). Sin embargo, una MatrizCuadrada es un ente abstracto. Representa a todas las matrices cuadradas (de dos por dos, de tres por tres, de n por n). Nosotros necesitamos una matriz de dos por dos (porque nuestra multiplicacin en matrices de dos por dos slo est denida para matrices de dos por dos). Eso que hicimos (el (Matriz2x2)) se llama en ingls casting. Es sencillamente ver a nuestro objeto m, como objeto de la clase Matriz2x2. Es como ponerle una mscara o que represente un cierto papel. Con el objeto m2x2 (que es idntico al objeto m, pero con mscara de Matriz2x2), ya podemos tener acceso a sus variables de clase (que son las que nos importan).

Interfaces y clases por dentro

53

Noten que usamos al operador . (punto) para tener acceso a las variables de clase del objeto m2x2, que es el parmetro que recibe el mtodo, con mscara de Matriz2x2. Una nota respecto al parmetro m. Como todos los parmetros, es una copia del valor que le pasaron. Pero los valores de las referencias son direcciones de memoria. As que aunque esta direccin de memoria sea una copia de la que le pasaron, la direccin en s es la misma. Hace referencia al mismo objeto (a la misma matriz) que se le pas al mtodo; por lo tanto, si modicamos la matriz dentro de la funcin, s se afecta al objeto con el que se llam la funcin. Hay que tener eso en cuenta. El polimorsmo tambin se aplica a los constructores. Podemos tener tantos constructores como queramos, siempre que reciban un nmero de parmetros distinto o que sus tipos sean diferentes. Por ejemplo, podemos tener un constructor para nuestra matriz cero:
15 16 17 18 19 20 21 public M a t r i z 2 x 2 ( ) { this . a = 0; this . b = 0; this . c = 0; this . d = 0; this . determinante = 0; }

Si slo vamos a tener un constructor, y ste slo va a poner en cero las variables (o en null, o en false), podemos no hacerlo. Java proporciona un constructor por omisin, que no recibe parmetros, y que inicializa todas las variables de clase, con cero si son numricas; con false si son booleanas; con \u0000 si son carcteres y con null si son referencias. Sin embargo, este constructor slo est disponible si no se deni ningn otro constructor en la clase. Adems, en estas prcticas siempre haremos constructores para nuestras clases, aunque hagan lo mismo que el constructor por omisin: es una buena prctica de programacin. Con todos estos conocimientos, hacer el resto de las operaciones para matrices de dos por dos es trivial.

Actividad 4.2 Haz los mtodos suma, resta e inversa para tu clase Matriz2x2. Las primeras dos son triviales (puedes basarte en el mtodo para multiplicar matrices). Para obtener la matriz inversa, primero comprueba que exista, o sea que el determinante sea distinto de cero (utiliza un if ), si no, una divisin por cero har que tu programa no funcione.

54

Mtodos get y set


El ltimo mtodo de la clase Matriz2x2 que hay que implementar es el mtodo
getDeterminante. Como ya deben haber adivinado, sirve para obtener el valor del deter-

minante de nuestra matriz. Su implementacin es sencillamente:


public double ge t D e t e r m i n a n t e ( ) { return determinante ; }

1 2 3

Por qu llamarlo getDeterminante y no dameDeterminante? Porque en Java existe la convencin de que si tenemos una variable llamada x, entonces el mtodo para obtenerla se llamara getX y el mtodo para cambiarle el valor ser setX (recuerdan el mtodo setHora?). Slo se est siguiendo la convencin de Java.

Clases de uso
Para probar nuestra interfaz MatrizCuadrada y nuestra clase Matriz2x2 podramos sencillamente colocarle un mtodo main a esta ltima, como los que hemos usado. Empero, aunque esto es til para probar inicialmente una clase, siempre hay que probarla desde otra clase. La razn es que siempre que hagamos una clase, debemos hacerla pensando que es un componente ms de algn problema (y si tenemos suerte, de varios problemas). Para que un componente sea realmente til, debe poder incluirse en cualquier proyecto sin que haya que hacerle modicaciones, y si slo probamos una clase dentro de ella misma, lo ms seguro es que esta modularidad no sea bien comprobada. Para probar nuestras clases, y para hacer nuestros programas, siempre utilizaremos clases de uso. Las clases de uso nunca tendrn variables de clase ni constructores. Muchas veces incluso consistirn slo de una funcin main, como la clase UsoReloj de las primeras dos prcticas (para distinguirlas de nuestras clases normales, a nuestras clases de uso les vamos a aadir el prejo Uso). Nuestra clase UsoMatrizCuadrada quedara como se muestra en la siguiente pgina.

Interfaces y clases por dentro

55

1 class UsoMatrizCuadrada { 2 public s t a t i c void main ( S t r i n g [ ] args ) { 3 MatrizCuadrada m1, m2; 4 m1 = new M a t r i z 2 x 2 ( 1 , 2 , 3 , 4 ) ; 5 m2 = new M a t r i z 2 x 2 ( 5 , 6 , 7 , 8 ) ; 6 7 MatrizCuadrada m u l t ; 8 m u l t = m1. m u l t i p l i c a (m2 ) ; 9 } 10 }

Pueden compilarla y ejecutarla (si ya acabaron la clase Matriz2x2. Parecer que no hace nada, pero s hace; lo que pasa es que no hemos creado un medio para mostrar nuestras matrices en la consola (y de hecho, ni siquiera declaramos una consola en nuestra clase de uso). Para mostrar nuestras matrices en la consola, podramos crear el mtodo imprime en la clase Matriz2x2:
50 51 52 53 54 55 public void imprime ( Consola c ) { c . imprimeln ( this . a ) ; c . imprimeln ( this . b ) ; c . imprimeln ( this . c ) ; c . imprimeln ( this . d ) ; }

Tendramos por supuesto que aadir import icc1_1.interfaz.Consola; a la clase Matriz2x2 para que compilara. Sin embargo, esto es un error. Si metemos el mtodo imprime en nuestra clase Matriz2x2, entonces ya no sera una clase independiente; dependera de la clase Consola. A cualquier lugar que llevramos nuestra clase Matriz2x2, tendramos que llevar tambin a la clase Consola. A veces no podr evitarse eso; una clase depender de otras muchas. Pero en este caso en particular, podemos hacer independiente a la clase Matriz2x2 de la clase Consola. Para ello, en lugar de pasarle toda una consola a la clase Matriz2x2 para que se imprima, mejor hagamos que la clase Matriz2x2 pueda ser representada en una cadena para que la imprimamos con la clase Consola. Esto lo logramos implementando el mtodo toString:
50 51 52 53 public S t r i n g t o S t r i n g ( ) { r e t u r n t h i s . a + "\t" + t h i s . b + "\n" + t h i s . c + "\t" + t h i s . d ; }

56

El modicador public es obligatorio. Porqu debe llamarse exactamente toString lo explicaremos cuando veamos herencia, y en un momento veremos porqu estamos sumando dobles con cadenas. Por ahora, con este mtodo en la clase, podemos imprimir nuestras matrices haciendo sencillamente:
Consola c ; c = new Consola ( "Matrices" ) ; MatrizCuadrada m; m = new M a t r i z 2 x 2 ( 1 , 2 , 3 , 4 ) ; c . i m p r i m e l n (m) ; / / Podemos pasar a l a m a t r i z d i r e c t a m e n t e .

Nuestra clase de uso quedara ahora as:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import i c c 1 _ 1 . i n t e r f a z . Consola ; class UsoMatrizCuadrada { public s t a t i c void main ( S t r i n g [ ] args ) { Consola c ; c = new Consola ( "Matrices" ) ; MatrizCuadrada m1, m2; m1 = new M a t r i z 2 x 2 ( 1 , 2 , 3 , 4 ) ; m2 = new M a t r i z 2 x 2 ( 5 , 6 , 7 , 8 ) ; c . i m p r i m e l n (m1 ) ; c . i m p r i m e l n (m2 ) ; MatrizCuadrada m u l t ; m u l t = m1. m u l t i p l i c a (m2 ) ; c . imprimeln ( mult ) ; } }

Interfaces y clases por dentro Actividad 4.3 Basndote en las prcticas anteriores, crea tu propio archivo build.xml. Pon el cdigo fuente en un directorio llamado src. No estamos usando paquetes, as que la tarea java ser as:
1 < j a v a classname="UsoMatrizCuadrada" f o r k ="true">

57

y la tarea javadoc quedara:


1 <javadoc sourcepath ="src" d e s t d i r ="docs" / >

Compila y ejecuta usando tu build.xml. Si quieres compilar dentro de XEmacs, utiliza uno de los archivos pjr.el de las prcticas anteriores, y ponlo junto al archivo build.xml.

Modicadores de acceso
Qu pasa si hacemos lo siguiente en el mtodo main de nuestra clase de uso?
4 5 6 7 8 9 10 11 12 13 14 public s t a t i c void main ( S t r i n g [ ] args ) { Consola c ; c = new Consola ( "Matrices" ) ; MatrizCuadrada m; m = new M a t r i z 2 x 2 ( 1 , 2 , 3 , 4 ) ; m. a = 1 0 ; c . i m p r i m e l n (m. g e t D e t e r m i n a n t e ( ) ) ; } }

El valor que se imprima en la consola ser -2, aunque el determinante de nuestra matriz (despus de la modicacin que le hicimos en la lnea 11) sea 34, porque el determinante se calcula en el momento en que el objeto se construye. Lo que pasa aqu es que el estado de nuestro objeto queda inconsistente. Y eso pasa porque modicamos directamente una variable de clase de nuestro objeto m al hacer
m. a = 1 0 ;

Podramos hacer la solemne promesa de nunca modicar directamente una variable de clase; pero eso no basta. Tenemos que garantizar que eso no ocurra. No debe ocurrir nunca.

58 A esto se le llama el acceso de una variable de clase (que no es lo mismo que el alcance). Una variable puede tener acceso pblico, de paquete, protegido o privado. Las variables locales no tienen modicadores de acceso ya que slo existen dentro de un mtodo. Cuando una variable de clase tiene acceso pblico, en cualquier mtodo de cualquier clase puede ser vista y modicada. Cuando tiene acceso privado, slo dentro de los mtodos y constructores de los objetos de la clase a la que pertenece puede ser vista y modicada. Los accesos de paquete y protegidos los veremos cuando veamos paquetes y herencia, respectivamente. Restringir el acceso a una variable de clase nos permite controlar cmo y cundo ser modicado el estado de un objeto. Con esto garantizamos la consistencia del estado, pero tambin nos permite cambiar las variables privadas de un objeto, y que ste siga siendo vlido para otras clases que lo usen; como nunca ven a las variables privadas, entonces no importa que las quitemos, les cambiemos el nombre o tipo o le aadamos alguna. El acceso es el principal factor del encapsulamiento de datos. Por todo esto, siempre es bueno tener las variables de clase con un acceso privado. Vamos a cambiarle el acceso a las variables de clase de Matriz2x2. Nuestras variables no tenan ningn acceso especicado, por lo que Java les asigna por omisin el acceso de paquete; para motivos prcticos y con lo que hemos visto, el acceso es pblico. Para cambirselo a privado, slo tenemos que hacer
3 class M a t r i z 2 x 2 implements MatrizCuadrada { 4 p r i v a t e double a ; 5 p r i v a t e double b ; 6 p r i v a t e double c ; 7 p r i v a t e double d ; 8 p r i v a t e double d e t e r m i n a n t e ;

Los modicadores para pblico, protegido y de paquete son public, protected y package respectivamente. Aunque no es necesario poner package para que una variable de clase tenga acceso de paquete, en estas prcticas ser requisito que el acceso de los mtodos y las variables sea explcito. Los mtodos tambin tienen modo de acceso, y tambin es de paquete por omisin. Hemos usado public porque queramos tener completa nuestra interfaz antes de implementar nuestra clase. El acceso en los mtodos tiene el mismo signicado que en las variables de clase; si un mtodo es privado, no podemos usarlo ms que dentro de los mtodos de la clase, y si un mtodo es pblico, se puede usar en cualquier mtodo de cualquier clase. Los mtodos privados son generalmente funciones auxiliares de los mtodos pblicos y de paquete, y al hacerlos privados podemos agregar ms o quitarlos cuando queramos, y las clases que usen a nuestra clase no se vern afectadas. El encapsula-

Interfaces y clases por dentro

59

miento de datos facilita la modularizacin de cdigo. Tambin ser requisito que todos los mtodos tengan el acceso explcito, aunque sea de paquete. Los constructores tambin tienen modo de acceso, y como en muchas otras cosas funciona de manera idntica que para mtodos. Sin embargo, si tenemos un constructor privado, signica que no podemos construir objetos de esa clase fuera de la misma clase; no podramos construir objetos de la clase en una clase de uso, por ejemplo. Veremos en un momento por qu quisisemos hacer eso. No slo las variables de clase, los mtodos y constructores tienen modo de acceso. Las mismas clases tienen modo de acceso, y tambin por omisin es de paquete. Una clase privada parece no tener sentido; y no obstante lo tiene. Veremos ms adelante cundo podemos hacer privado el acceso a una clase; por ahora, cambiemos el acceso de nuestra clase a pblico:
3 public class M a t r i z 2 x 2 implements MatrizCuadrada { 4 ...

Ya que nuestras variables de clase son privadas, cmo hacemos para poder verlas o modicarlas fuera de la clase? Como dijimos arriba, Java tiene la convencin de utilizar mtodos set/get para esto. Los mtodos set cambiarn el valor de una variable, y los get lo obtendrn. Por ejemplo, para la variable a:
70 71 72 73 74 75 76 77 78 79 public double getA ( ) { return this . a ; } public void setA ( double a ) { this . a = a ; t h i s . d e t e r m i n a n t e = t h i s . a * t h i s . dt h i s . b * t h i s . c ; } ...

Puede parecer engorroso; pero la justicacin se hace evidente en esta ltima funcin. Cada vez que modiquemos un componente de la matriz, actualizaremos automticamente el valor del determinante. Con esto dejamos bien denido cmo ver y cmo modicar el estado de un objeto, y garantizamos que sea consistente siempre. El acceso es parte fundamental del diseo de una clase. Si hacemos un mtodo o variable de clase con acceso pblico, habr que cargar con l siempre, ya que si lo quitamos o lo hacemos privado, es posible que existan aplicaciones que lo utilizaran, y entonces stas ya no serviran.

60
Actividad 4.4 Implementa los mtodos getA, getB, getC, getD y setA, setB, setC y setD. Puedes observar que no habr mtodo setDeterminante, ya que el determinante depende de las componentes de la matriz.

Otros modicadores
Hay dos modicadores ms para las variables y mtodos de Java. El primero de ellos es nal . Una variable nal slo puede ser inicializada una vez; y generalmente ser al momento de ser declarada. Una vez inicializada, ya no cambia de valor nunca. Su valor es nal. Las variables nales pueden ser de clase o bien locales. Cuando tengamos una literal que utilizamos en muchas partes del cdigo, y que dentro de la ejecucin del programa nunca cambia (por ejemplo, el IVA en un programa de contabilidad), siempre es mejor declarar una variable nal para denirlo, y as si por alguna razn nos vuelven a subir el IVA,3 en lugar de cambiar todos los lugares donde aparezca la literal, slo cambiamos el valor que le asignamos a la variable nal. Tambin facilita la lectura del cdigo. Otros lenguajes de programacin tienen constantes. Las variables nales de Java no son constantes exactamente; pero pueden emular a las constantes de otros lenguajes. Hay mtodos nales tambin, pero su signicado lo veremos cuando veamos herencia. El otro modicador es static . Una variable de clase esttica es la misma para todos los objetos de una clase. No es como las variables de clase normales, que cada objeto de la clase tiene su propio cajn; las variables estticas son compartidas por todos los objetos de la clase, y si un objeto la modica, todos los dems objetos son afectados tambin. No hay variables locales estticas (no tendran sentido). Tambin hay mtodos estticos. Un mtodo esttico no puede tener acceso a las variables de clase no estticas; no puede hacer this. a ninguna variable. El mtodo main por ejemplo siempre es esttico. Para tener acceso a una variable esttica o a un mtodo esttico se puede utilizar cualquier objeto de la clase, o la clase misma. Si la clase Matriz2x2 tuviera una variable esttica var por ejemplo, podramos hacer Matriz2x2.var para tener acceso a ella (si var fuese pblica, por supuesto). Hay clases cuyos objetos necesitan pasar por un proceso de construccin ms estricto que las clases normales. Para este tipo de clases, que suelen llamarse clases fbrica (o factory classes en ingls), lo que se hace es que tienen constructores privados, y
3

O ms raro; que lo bajen.

Interfaces y clases por dentro

61

entonces se usan mtodos estticos para que creen los objetos de la clase usando las restricciones que sean necesarias. Si queremos una variable privada, que adems sea esttica, y adems nal, hay que hacer
public s t a t i c f i n a l i n t n = 0 ; / / Hay que i n i c i a l i z a r s i es f i n a l !

por ejemplo. No se puede cambiar el orden; lo siguiente no compila


public f i n a l s t a t i c i n t n = 0 ; / / Hay que i n i c i a l i z a r s i es f i n a l !

Bsicamente eso es todo lo necesario para escribir una clase.

Cadenas
En la prctica anterior vimos tipos bsicos, pero varios de ustedes han de haber observado que constantemente mencionbamos cadenas sin nunca denirlas. Por ejemplo, al constructor de la clase Consola le pasamos una cadena:4
Consola c ; c = new Consola ( "Ttulo de mi consola" ) ;

Las cadenas (strings en ingls) son objetos de Java; pero son los nicos objetos que tienen literales distintas de null y denido el operador +. Adems, son los nicos objetos que podemos crear sin utilizar new. Las cadenas son consideradas por todo esto un tipo bsico; pero no por ello dejan de ser objetos, con todos los atributos y propiedades de los objetos. Las literales de las cadenas las hemos usado a lo largo de estas prcticas; son todos los carcteres que estn entre comillas: "hola mundo" ; "adis..." ; "En un lugar de la Mancha de cuyo nombre..." ; Para declarar una variable de tipo cadena, declaramos un objeto de la clase String:
String a;
4

Observa que estamos utilizando

para representar los espacios dentro de las cadenas.

62

pero no es necesario construirlo con new; el asignarle una literal de cadena basta. Las dos construcciones de la siguiente pgina son equivalentes.
a = "hola mundo" ; a = new S t r i n g ( "hola mundo" ) ;

Esta es una comodidad que Java otorga; pero en el fondo las dos construcciones son exactamente lo mismo (en ambas la JVM asigna memoria para el objeto), aunque en la primera no tengamos que escribir new String ( ) . Adems de tener literales y construccin implcita, las cadenas son el nico objeto en Java que tiene denido el operador +, y sirve para concatenar cadenas:
S t r i n g h , m, a ; h = "hola " ; m = "mundo" ; a = h + m;

En este ejemplo, la variable a termina valiendo "hola mundo". Lo bonito del caso es que no slo podemos concatenar cadenas; podemos concatenar cadenas con cualquier tipo de Java. Si hacemos
i n t edad = 1 5 ; String a; a = "Edad: " + 1 5 ;

la cadena resultante es " Edad: 15". Y funciona para enteros, otantes, booleanos carcteres e incluso referencias. Por eso nuestro mtodo toString poda sumar dobles con cadenas; realmente estaba concatenando cadenas. Por dentro, una cadena es una sucesin de caracteres uno detrs del otro. La cadena a que vimos arriba tiene esta forma por dentro:
h
0

o
1

l
2

a
3 4

m u
5 6

n
7

d
8

o
9

Las cadenas son objetos de slo lectura. Esto quiere decir que podemos tener acceso al carcter m de la cadena "hola mundo", pero no podemos cambiar la m por t para que esa misma cadena sea "hola tundo". Sin embargo, podemos crear un nuevo objeto cadena que diga "hola tundo" a partir de "hola mundo", y asignarlo de nuevo a la variable:

Interfaces y clases por dentro


String a; a = "hola mundo" ; a = a . s u b s t r i n g ( 0 , 5 ) + "t" + a . s u b s t r i n g ( 6 , 1 0 ) ;

63

Ahora a representa a "hola tundo"; pero hay que entender que a a se le asign un nuevo objeto (construido a partir del original), no que se modic el anterior (y adems, utiliza tres objetos temporales para construir el nuevo objeto; el primero a.substring (0,5) , el segundo "t", y el tercero a.substring (6,10) ). Las cadenas son parte fundamental de la aritmtica de Java; ofrecen muchas funciones y utilidades que en muchos otros lenguajes de programacin es necesario implementar a pie. Regresando a la cadena "hola tundo", su longitud es 10 (tiene 10 carcteres), y siempre puede saberse la longitud de una cadena haciendo
int longitud = a . length ( ) ;

Las cadenas pueden ser tan grandes como la memoria lo permita. Cada uno de los carcteres de la cadena pueden recuperarse (leerse), pero no modicarse. Java ofrece adems muchas funciones para construir cadenas a partir de otras cadenas. Por ejemplo, si queremos slo la subcadena "tundo", slo necesitamos hacer
String b; b = a . substring (5 ,10);

De nuevo; esto regresa un nuevo objeto sin modicar el anterior. La cadena a seguir siendo "hola tundo". Hay que notar que substring regresa todos los carcteres entre el ndice izquierdo y el ndice derecho menos uno sin incluir al carcter en la posicin del ndice derecho . Esto es para que la longitud de la subcadena sea el ndice derecho menos el ndice izquierdo. Puede que slo deseemos un carcter, entonces podemos usar
char c = a . c h a r A t ( 5 ) ;

Y esto regresa el carcter (tipo char) m. Si tratamos de leer un carcter en una posicin invlida (menor que cero o mayor o igual que la longitud de la cadena), el programa terminar con un error. Las variables de la clase String son referencias, como todos los objetos. Eso quiere decir que cuando hagamos

64
S t r i n g a = "hola mundo" ; S t r i n g b = "hola mundo" ; boolean c = ( a == b ) ;

el boolean c tendr como valor false. Las variables a y b son distintas; apuntan a direcciones distintas en la memoria. Que en esas direcciones haya cadenas idnticas no le importa a Java; lo importante es que son dos referencias distintas. Si queremos comparar dos cadenas lexicogrcamente, debemos usar el mtodo equals de la clase String:
c = a . equals ( b ) ;

En este caso, el valor de c s es true. Las literales de cadena en Java son objetos totalmente calicados. Por lo tanto, es posible llamar mtodos utilizndolas (por bizarro que se vea):
i n t l = "hola mundo" . l e n g t h ( ) ; String b; b = "hola mundo" . s u b s t r i n g ( 0 , 4 ) ;

La clase String de Java ofrece muchas otras funciones tiles, que iremos aprendiendo a usar a lo largo de estas prcticas.

Actividad 4.5 Mediante un navegador, consulta la documentacin de la clase String. Necesitars estarla consultando para los ejercicios de esta prctica.

El recolector de basura
Hemos dicho que cuando utilizamos el operador new para construir un objeto, la JVM le asigna una porcin de la memoria para que el objeto sea almacenado. Tambin dijimos que las variables de clase viven desde que el objeto es construido hasta que ste deja de existir. Y en el ejemplo de "hola mundo" dijimos que al hacer
String a; a = "hola mundo" ; a = a . s u b s t r i n g ( 0 , 5 ) + "t" + a . s u b s t r i n g ( 6 , 1 0 ) ;

Interfaces y clases por dentro

65

estbamos usando tres objetos temporales (las tres subcadenas que forman "hola t undo"). Qu pasa con esas subcadenas? Cundo deja de existir un objeto? Si new hace que la JVM asigne memoria, cundo se libera esta memoria? La respuesta corta es no hay que preocuparse por ello. La larga es as: imagnense que cada objeto tiene un contador interno que se llama contador de referencias. Cuando un objeto se construye con new (aunque sea implcitamente, como las cadenas), ese contador se pone en cero. En cuanto una referencia comienza a apuntar hacia el objeto, el contador aumenta en uno. Por cada referencia distinta que empiece a apuntar hacia el objeto, el contador se aumentar en uno. Por ejemplo
String a; a = "cadena" ; String b , c , d , e; b = c = d = e = a;

En este ejemplo, al objeto de la clase cadena que instanciamos como adena", su contador de referencias vale 5, ya que a, b, c, d y e, apuntan hacia l. Cada vez que una referencia deje de apuntar al objeto, el contador disminuye en 1. Si ahora hacemos
a = b = c = d = e = null ;

con eso las cinco referencias dejan de apuntar al objeto, y entonces su contador de referencias llega a cero. Cuando el contador de referencias llega a cero, el objeto se marca como removible. Un programa especial que corre dentro de la JVM (el recolector de basura) se encarga cada cierto tiempo de buscar objetos marcados como removibles y regresar al sistema la memoria que utilizaban. Debe quedar claro que ste es un ejemplo simplicado de cmo ocurre realmente la recoleccin de basura; en el fondo es un proceso mucho ms complejo y el algoritmo para marcar un objeto como removible es mucho ms inteligente. Pero la idea central es sa; si un objeto ya no es utilizado, que el recolector se lo lleve. La respuesta corta es suciente sin embargo. La idea del recolector de basura es que los programadores (ustedes) no se preocupen de liberar la memoria que asignan a sus objetos. En otros lenguajes eso tiene mucha importancia; mas por suerte Java lo hace por nosotros.

66

Comentarios para JavaDoc


Nuestra interfaz MatrizCuadrada y nuestra clase Matriz2x2 estn terminadas y funcionan. Otros programadores podran querer utilizarlas para sus propios programas. Para ayudarlos a entender cmo funciona la clase, podemos hacer disponibles para ellos la documentacin generada por JavaDoc.

Actividad 4.6 Genera la documentacin de JavaDoc utilizando el objetivo docs de tu build.xml. Con la ayuda de un navegador revisa la documentacin que se genera.

Puedes ver que la documentacin generada es bastante explcita por s misma. Nos dice el tipo de regreso de los mtodos, as como sus parmetros, y qu constructores tiene la clase. Con esa informacin, cualquiera que sepa lo que nosotros sabemos hasta el momento, puede empezar a trabajar con una clase. Sin embargo, podemos hacer todava ms para que la documentacin sea an ms explcita. Podemos utilizar comentarios especiales para que JavaDoc haga la documentacin ms profunda. Para que JavaDoc reconozca que un comentario va dirigido hacia l, necesitamos comenzarlos con /* * en lugar de slo /* . Despus de eso, slo necesitamos poner el comentario inmediatamente antes de la declaracin de un mtodo, un constructor o de una clase para que JavaDoc lo reconozca. Modica as la declaracin de la clase
Matriz2x2 3 4 5 6 7 8 9 /* *

* Clase para r e p r e s e n t a r m a t r i c e s de dos r e n g l o n e s * por dos columnas , que implementa l a i n t e r f a z * MatrizCuadrada . */ public class M a t r i z 2 x 2 implements MatrizCuadrada { ...

y modica as la declaracin del mtodo multiplica (el que multiplica por escalares):
15 16 17 18 19 * C a l c u l a e l r e s u l t a d o de m u l t i p l i c a r l a m a t r i z por * un e s c a l a r . */ public MatrizCuadrada m u l t i p l i c a ( double x ) { /* *

Interfaces y clases por dentro


20 ...

67

Vuelve a generar la documentacin de la clase y consltala para que veas los resultados. Para mtodos, podemos mejorar todava ms la documentacin utilizando unas etiquetas especiales. Vuelve a modicar el comentario del mtodo multiplica, pero ahora as:
15 16 17 18 19 20 21 22 23 24 * C a l c u l a e l r e s u l t a d o de m u l t i p l i c a r l a m a t r i z por * un e s c a l a r . * @param x e l e s c a l a r por e l que se m u l t i p l i c a r la matriz . * * @return l a m a t r i z r e s u l t a n t e de m u l t i p l i c a r e s t a m a t r i z por e l e s c a l a r x . * */ public MatrizCuadrada m u l t i p l i c a ( double x ) { ... /* *

Vuelve a generar la documentacin y comprueba los cambios. La etiqueta @param le sirve a JavaDoc para saber que vamos a describir uno de los parmetros del mtodo. El nombre del parmetro debe corresponder a alguno de los parmetros que recibe el mtodo, obviamente. Si no, JavaDoc marcar un error, aunque generar la documentacin de todas maneras. La etiqueta @return le dice a JavaDoc que se va a describir qu es lo que regresa el mtodo. Todos los mtodos que escribas de ahora en adelante debern estar documentados para JavaDoc, con su valor de regreso (si no es void), y cada uno de los parmetros que reciba, si recibe. Las clases tambin tienen que estar documentadas.

Ejercicios
Entre las aplicaciones ms utilizadas en el mundo de la computacin estn las bases de datos. A partir de esta prctica, iremos recolectando todo lo que aprendamos para construir poco a poco una bases de datos para una agenda. Una base de datos tiene registros, que a su vez estn compuestos de campos. Los campos de nuestra base de datos para una agenda sern nombre, direccin y telfono y cada registro representar a un individuo. Por ahora empezaremos con una base de datos ya llena, lo que quiere decir que los datos ya estn dados y que no necesitars introducirlos t mismo.

68 Aunque hay muchas formas de implementar bases de datos, una de las ms comunes es utilizar una tabla; los renglones son los registros, y las columnas campos, como en Nombre Juan Prez Garca Arturo Lpez Estrada Edgar Hernndez Levi Mara Garca Snchez Pedro Pramo Rulfo Jos Arcadio Buenda Florentino Ariza Galio Bermdez Carlos Garca Vigil Eligio Garca Agusto Direccin Avenida Siempre Viva # 40 Calle de la abundancia # 12 Oriente 110 # 14 Avenida Insurgentes Sur # 512 Avenida Mxico Lindo # 23 Macondo # 30 Calle de la Clera # 11 Stanos de Mxico # 45 La Repblica # 1 Ciudades Desiertas # 90 Telfono 55554466 55557733 55512112 56742391 54471499 56230190 55551221 55552112 55554332 56344325

Para representar a todos los datos, utilizaremos una sola variable esttica de tipo cadena, llamada tabla. Los registros (o renglones) son de tamao jo, as que si queremos el primer registro slo necesitamos hacer
t a b l a . s u b s t r i n g ( 0 ,TAM_REGISTRO ) ;

Donde TAM_REGISTRO es otra variable, esttica y nal, que tiene el tamao del registro. Si necesitamos el segundo registro, tenemos que hacer
t a b l a . s u b s t r i n g (TAM_REGISTRO, TAM_REGISTRO+TAM_REGISTRO ) ;

En general, si necesitamos el i-simo registro (donde el primer registro es el registro 0, como buenos computlogos), hacemos
t a b l a . s u b s t r i n g ( i * TAM_REGISTRO, i * TAM_REGISTRO+TAM_REGISTRO ) ;

o lo que es lo mismo
t a b l a . s u b s t r i n g ( i * TAM_REGISTRO, ( i + 1 ) * TAM_REGISTRO ) ;

Para obtener los campos se necesita algo similar usando los tamaos de cada campo. Nota que estamos usando TAM_REGISTRO solo, sin hacer this.TAM_REGISTRO o BaseDeDatosAgenda.TAM_REGISTRO. Esto es porque estamos dentro de la clase

Interfaces y clases por dentro

69

BaseDeDatosAgenda; cuando usamos TAM_REGISTRO el compilador asume que habla-

mos de las variables de la clase donde estamos. Por ahora, supondremos que todos los campos son nicos; o sea que no se repiten nombres, ni direcciones ni telfonos. 1. Por ahora, nuestra base de datos de agenda ser slo de lectura, ya que no insertaremos nuevos registros ni borraremos ninguno. Sin embargo s leeremos datos, y de hecho podremos hacer bsquedas. Abre el archivo BaseDeDatosAgenda.java donde est el esqueleto de lo que hemos estado discutiendo. Por ahora slo tiene dos funciones (vacas); dameRegistroPorNombre y dameRegistroPorTelefono. La primera recibe una cadena con el nombre a buscar y la segunda recibe un entero con el telfono. Ambas regresan una cadena, que es el registro que caza la bsqueda, o null si el nombre o telfono no estn en la base de datos. Por ejemplo, si hacemos
BaseDeDatosAgenda bdda ; S t r i n g busqueda ; bdda = new BaseDeDatosAgenda ( ) ; busqueda = bdda . dameRegistroPorNombre ( "Pedro Pramo Rulfo" ) ;

Entonces la cadena busqueda debe valer


"Pedro Pramo Rulfo Avenida Mxico Lindo 23 54471499"

Y lo mismo si hacemos
busqueda = bdda . dameRegistroPorTelefono ( 5 4 4 7 1 4 9 9 ) ;

Utilizando las funciones de la clase String, implementa los mtodos. Una pista para ayudar: vas a necesitar convertir enteros en cadenas. Piensa que necesitars el valor de un entero como cadena. Y vas a necesitar encontrar dnde empieza una subcadena dentro de otra cadena. Lee todos los mtodos de la clase cadena (en la documentacin generada de la clase String), antes de utilizar alguno. 2. Queremos utilizar nuestra base de datos, as que escribe una clase de uso llamada UsoBaseDeDatosAgenda, que slo tenga la funcin main y utilice las funciones que has escrito. El esqueleto de la clase est ya en el archivo UsoBaseDeDatosAgenda.

70 Dentro de main debes crear un objeto de la clase BaseDeDatosAgenda, realizar una bsqueda por nombre, imprimir el resultado, realizar una bsqueda por telfono e imprimir el resultado. Utiliza la clase Consola. 3. Utiliza comentarios de JavaDoc en los dos mtodos dameRegistroPorNombre y dameRegistroPorTelefono para documentar qu es lo que deben hacer. 4. Los archivos que te fueron proporcionados no incluyen un build.xml. Basndote en los utilizados en prcticas anteriores, crea el tuyo propio.

Preguntas
1. Qu otras funciones debe implementar una base de datos de agenda? Explica. 2. Podramos implementarlas con lo que sabemos hasta ahora? Justica. 3. Qu crees que necesitaramos? Explica.

Prctica: Estructuras de control y listas

A hardware failure will cause system software to crash, and the customer engineer will blame the programmer. Murphys Laws of Computer Programming #5

Meta
Que el alumno aprenda a utilizar condicionales, iteraciones y listas.

Objetivos
Al nalizar la prctica el alumno ser capaz de: utilizar condicionales (switch, if ); utilizar iteraciones (while, do ... while, for);

72 utilizar listas ligadas y comprender qu son los paquetes en Java.

Desarrollo
Un mtodo no es slo una serie de instrucciones ejecutndose una por una. Un mtodo tiene que tomar decisiones a partir de los parmetros que reciba. Hemos utilizado el if para hacer esto; pero no lo hemos denido formalmente. Adems de tomar decisiones, un mtodo puede necesitar repetir una tarea un determinado nmero de veces; o mientras cierta condicin se cumpla. En esta prctica, aprenderemos cmo nuestros mtodos pueden tomar decisiones (varias decisiones a la vez, de hecho), y cmo repetir tareas utilizando condicionales e iteraciones (tambin conocidas como ciclos). Con estos nuevos conocimientos podremos comenzar a utilizar una de las estructuras ms poderosas que hay en computacin (independientemente del lenguaje): las listas simplemente ligadas.

Condicionales
En la prctica anterior, cuando una de nuestras bsquedas fallaba, el mtodo deba regresar null. Muchas funciones (y muchas ms que escribiremos) regresarn un valor especial cuando algo no funcione exactamente como esperbamos. Hay que saber cundo se regresan estos valores especiales y cmo tratarlos. se es un ejemplo particular. En general, un mtodo debe ser capaz de tomar decisiones de acuerdo a los parmetros que reciba. Para tomar decisiones, Java ofrece dos condicionales: switch e if .

La condicional switch
La condicional switch nos sirve para tomar mltiples decisiones a partir de una expresin de tipo int , short, byte o char. Supongamos que tenemos una clase Prueba con las siguientes variables de clase:
public public public public public static static static static static final final final final final int int int int int SOLTERO CASADO DIVORCIADO VIUDO OTRO = = = = = 1; 2; 3; 4; 5;

Estructuras de control y listas

73

Con esto podemos clasicar de manera legible los distintos estados civiles. Y podemos usar un switch para tomar decisiones (si suponemos que el mtodo dameEstadoCivil de alguna manera obtiene el estado civil):
Consola c ; c = new Consola ( ) ; Prueba p ; p = new Prueba ( ) ; ... / / Aqu hacemos v a r i a s cosas con p .

i n t e s t a d o C i v i l = p . dameEstadoCivil ( ) ; switch ( e s t a d o C i v i l ) { case Prueba .SOLTERO: c . i m p r i m e l n ( "ste s es listo." ) ; break ; case Prueba .CASADO: c . i m p r i m e l n ( "A ste ya lo agarraron." ) ; break ; case Prueba . DIVORCIADO : c . i m p r i m e l n ( "ste recapacit." ) ; break ; case Prueba . VIUDO : c . i m p r i m e l n ( "ste tom decisiones extremas." ) ; break ; default : c . i m p r i m e l n ( "ste no quiere decir." ) ; }

La condicional switch slo recibe como selector expresiones de alguno de los tipos que dijimos arriba; no funciona con expresiones de punto otante, booleanas o siquiera de tipo long. Una vez que recibe la expresin, calcula su valor y comprueba si algn case caza con ese valor. Si caza, se ejecuta el cdigo a partir de los dos puntos, y hasta el nal del switch. Esto es importante; se ejecuta desde el case que caza con el valor recibido, y se siguen ejecutando todos los case que le siguen. Para evitar que se sigan ejecutando todos los case, ponemos el break. El break (como su nombre indica) rompe la ejecucin del bloque donde estemos. Si slo queremos que se ejecute uno de los case, siempre hay que poner break; pero

74 a veces querremos que se ejecute ms de un case. Si ningn case caza con el valor recibido, se ejecuta el default. No es obligatorio que exista un default, pero se recomienda para que absolutamente todos los casos queden cubiertos. Si no hay default, sencillamente el programa contina con la ejecucin del enunciado que le siga al switch. En este ejemplo, usamos variables nales para etiquetar los case. Es el nico tipo de variables que podemos usar: un case slo puede ser etiquetado por una variable nal o una literal (de los tipos que mencionamos arriba). La condicional switch nos permite tomar decisiones con varias opciones. Muchas veces, sin embargo, vamos a necesitar decidir slo entre s y no. Para eso est el if .

La condicional if ... else


Ya hemos utilizado la condicional if ; nos permite tomar decisiones binarias: s o no. Por ejemplo, para comprobar que la funcin dameRegistroPorNombre no regresara
null debamos hacer algo as: S t r i n g reg ; reg = bdd . dameRegistroPorNombre ( "Pedrito" ) ; i f ( reg == n u l l ) { c . i m p r i m e l n ( "No existe Pedrito en la base de datos." ) ; } else { c . i m p r i m e l n ( "El registro es:\n"+reg ) ; }

La condicional if recibe una expresin booleana. No recibe ningn otro tipo de expresin; ni enteros ni otantes ni carcteres. Slo recibe booleanos. Si el valor de la expresin que recibe es true, se ejecuta el primer bloque. Si es false, se ejecuta el bloque del else el segundo bloque . Un if puede existir sin else, por ejemplo (estamos suponiendo ahora otro mtodo que regresa una calicacin):
float c a l i f i c a c i o n = p . dameCalificacion ( ) ; c . i m p r i m e l n ( "Tu calificacin es: "+ c a l i f i c a c i o n +"." ) ; i f ( c a l i f i c a c i o n >= 8 ) { c . i m p r i m e l n ( "Felicidades." ) ; }

Estructuras de control y listas

75

La relacin entre switch e if


El if es slo una particularizacin del switch (o el segundo una generalizacin del primero, como preeran). En general, podramos usar solamente if ; por ejemplo el primer ejemplo que vimos:
Consola c ; c = new Consola ( ) ; Prueba p ; p = new Prueba ( ) ; i n t e s t a d o C i v i l = p . dameEstadoCivil ( ) ; i f ( e s t a d o C i v i l == Prueba .SOLTERO) { c . i m p r i m e l n ( "ste s es listo." ) ; } else i f ( e s t a d o C i v i l == Prueba .CASADO) { c . i m p r i m e l n ( "A ste ya lo agarraron." ) ; } else i f ( e s t a d o C i v i l == Prueba . DIVORCIADO ) { c . i m p r i m e l n ( "ste recapacit." ) ; } else i f ( e s t a d o C i v i l == Prueba . VIUDO ) { c . i m p r i m e l n ( "ste tom decisiones extremas." ) ; } else { c . i m p r i m e l n ( "ste no quiere decir." ) ; }

Las sangras que se muestran son las que corresponden a la manera como se ejecuta el if . Sin embargo, en este ejemplo, XEmacs (y cualquier otro editor de cdigo que se respete) indentar el cdigo como se muestra a continuacin, que corresponde a cmo se indentara de haberse trabajado el ejemplo con un switch. Hay que entender que el segundo if est anidado dentro del primero, y el tercero dentro del segundo, etc., como lo reeja el primer listado. Es por eso que el switch es mucho mejor para este tipo de casos. Pero existen ambas versiones para que se pueda utilizar la que ms nos convenga, dependiendo de nuestro problema.
Consola c ; c = new Consola ( ) ; Prueba p ; Contin en la siguiente pgina

76
Contina de la pgina anterior p = new Prueba ( ) ; i n t e s t a d o C i v i l = p . dameEstadoCivil ( ) ; i f ( e s t a d o C i v i l == Prueba .SOLTERO) { c . i m p r i m e l n ( "ste s es listo." ) ; } else i f ( e s t a d o C i v i l == Prueba .CASADO) { c . i m p r i m e l n ( "A ste ya lo agarraron." ) ; } else i f ( e s t a d o C i v i l == Prueba . DIVORCIADO ) { c . i m p r i m e l n ( "ste recapacit." ) ; } else i f ( e s t a d o C i v i l == Prueba . VIUDO ) { c . i m p r i m e l n ( "ste tom decisiones extremas." ) ; } else { c . i m p r i m e l n ( "ste no quiere decir." ) ; }

Iteraciones
Qu pasa si queremos imprimir los nombres de todos los registros en nuestra base de datos? Podemos hacerlo uno por uno, si suponemos que los conocemos todos a priori. O podemos utilizar iteraciones. Las iteraciones nos sirven para repetir tareas un determinado nmero de veces o hasta que alguna condicin falle.

Los ciclos while y do ... while


En el caso de nuestra base de datos para agenda, si no supisemos cuntos registros tiene, para imprimir los nombres de todos lo mejor sera usar un while. He aqu un ejemplo:
int i = 0; / / Tenemos que i n i c i a l i z a r l o porque l o usamos en e l w h i l e . S t r i n g reg = "" ; S t r i n g nombre ; Contina en la siguiente pgina

Estructuras de control y listas

77
Contina de la pgina anterior

while ( reg ! = n u l l ) { reg = bdd . dameRegistro ( i ) ; nombre = bdd . dameNombreDeRegistro ( reg ) ; c . i m p r i m e l n ( "Nombre: "+nombre ) ; i ++; }

(An no escribimos las funciones dameRegistro y dameNombreDeRegistro, pero no es muy difcil y deberan poder imaginarse cmo hacerlo.) La iteracin (o ciclo) while ejecuta su cuerpo mientras la expresin condicional que evala sea verdadera. La expresin que recibe el while tiene que ser booleana. Su estructura de control hermana, do ... while funciona de forma casi idntica:
int i = 0; S t r i n g reg ; S t r i n g nombre ; do { reg = bdd . dameRegistro ( i ) ; nombre = bdd . dameNombreDeRegistro ( reg ) ; c . i m p r i m e l n ( "Nombre: "+nombre ) ; i ++; } while ( reg ! = n u l l ) ;

La nica diferencia es que while comprueba primero la condicin y luego, en su caso, ejecuta el cuerpo. En cambio do ... while primero ejecuta el cuerpo y luego comprueba la condicin; esto quiere decir que el do ... while ejecuta siempre su cuerpo al menos una vez. El while y el do ... while son especialmente tiles cuando no sabemos de antemano cuntas veces vamos a iterar. Sin embargo, a veces s sabemos exactamente cuntas veces vamos a iterar, y en estos casos se suele usar el for.

La iteracin for
El for est pensado para iterar sobre rangos. Por ejemplo, para imprimir todos los nombres de nuestra base de datos para agenda haramos:

78

int i ; / / Con esto , calculamos e l nmero de r e g i s t r o s . i n t numeroDeRegistros = t a b l a . l e n g t h ( ) / TAM_REGISTRO ; S t r i n g reg ; S t r i n g nombre ; f o r ( i = 0 ; i < numeroDeRegistros ; i ++) { reg = bdd . dameRegistro ( i ) ; nombre = bdd . dameNombreDeRegistro ( reg ) ; c . i m p r i m e l n ( "Nombre: "+nombre ) ; }

La iteracin for recibe tres expresiones. La primera se ejecuta incondicionalmente, pero slo una vez. Generalmente se utiliza para inicializar variables que se usarn para recorrer algn rango. Esta primera expresin no necesita regresar valor. La segunda expresin es booleana. Mientras esta expresin se cumpla (o sea, regrese true), el cuerpo del for se ejecutar. Tiene que regresar obligatoriamente un valor de tipo booleano. La tercera expresin se utiliza generalmente para actualizar las variables inicializadas en la primera expresin. Tampoco necesita regresar ningn valor. Al entrar al for, se ejecuta primero la inicializacin, y se comprueba que la condicin booleana sea verdadera. Si no es verdadera, no se ejecuta el cuerpo nunca. Si s es verdadera, se ejecuta el cuerpo del for, y al nal se ejecuta el tercer argumento. Despus vuelve a comprobar la condicin booleana (la inicializacin ya no vuelve a considerarse) y si es verdadera se repite todo. El for, el while y do ... while son exactamente lo mismo. Un while puede escribirse con un for, y un for con un while; y si slo se tuviera while o do ... while se podra emular al otro. De hecho, hay lenguajes de programacin donde slo se da while o do ... while, no ambos. Tambin hay lenguajes donde no se da el for. Tener los tres nos permite mayor versatilidad al escribir cdigo; es bueno utilizar el que ms se acomode en cada caso.

Las instrucciones break, return y continue


La instruccin break nos sirve para romper la ejecucin de un switch, como ya vimos. Pero sirve tambin para romper cualquier bloque o cuerpo de un ciclo. Con el break podemos romper la ejecucin de un switch, de un while, de un do .. while y de

Estructuras de control y listas

79

un for. La instruccin continue en cambio se salta lo que reste de la vuelta actual, para empezar inmediatamente la siguiente. Habr veces dentro de un mtodo en que no slo querremos salir de un switch o un ciclo, sino que querremos salir de toda la funcin. Para tales casos hay que usar la instruccin return <expresin>. Podemos usar la instruccin return en cualquier mtodo, inclusive aquellos que tienen tipo de regreso void; en estos casos se utiliza el return solo, sin ninguna expresin a la derecha.

Listas
Las condicionales e iteraciones nos permiten tomar decisiones dentro de nuestro programa, lo que hace que el ujo de ejecucin sea tan complejo como deseemos. Ahora que las tenemos, es posible utilizar clases que nos permiten representar de forma ms clara la informacin de un programa. Las listas son un tipo abstracto de datos que nos permite tener un nmero arbitrario de algn tipo de elementos. Deniremos las listas como sigue

Una lista null (si la lista es vaca), o un elemento seguido de una lista.

La denicin es recursiva; hace referencia a lo que est deniendo. Piensen en los nmeros naturales y los axiomas de Peano; un nmero natural es 1, o un nmero natural ms 1. Por su misma denicin, las listas son divertidas para trabajar. Si una lista no tiene elementos, entonces decimos que es vaca y ser sencillamente null. Si no es vaca, entonces tiene al menos un elemento, y adems a su primer elemento le sigue una lista (con cero o ms elementos). Ms adelante trabajaremos con listas de muchos tipos; por ahora utilizaremos slo listas de cadenas utilizando la clase ListaDeCadena. Si una lista no es vaca, tiene dos partes: su primer elemento, que llamaremos cabeza y al que podremos tener acceso con el mtodo getElemento; y otra lista, a la cual le llamaremos la siguiente lista, y a la que podremos tener acceso con el mtodo getSiguiente:

80

ListaDeCadena a ; a = p . l l e n a L a L i s t a ( ) ; / / Llena l a l i s t a de alguna manera . i f ( a != null ) { c . i m p r i m e l n ( "Este es el primer elemento: " + a . getElemento ( ) ) ; a = a . getSiguiente ( ) ; i f ( a != null ) { c . i m p r i m e l n ( "Este es el siguiente elemento: " + a . getElemento ( ) ) ; } }

Vean el cdigo de arriba; ah perdimos al primer elemento. Al momento de hacer


a = a . getSiguiente ( ) ;

la nica referencia que apuntaba al primer elemento de la lista (a), hacemos ahora que apunte al siguiente elemento. Ya no hay referencias apuntando al primer elemento, ya no podemos usarlo. Eso signica que queda marcado como removible y que el recolector de basura se lo llevar. Y ya no hay manera de recuperarlo. Con las listas como las estamos usando, si tenemos a un elemento, podemos tener el que le sigue, pero no al anterior. Esto es importante; no pierdan nunca la cabeza (de las listas, por supuesto). Cuntos elementos tiene una lista? Es muy fcil saberlo:
ListaDeCadena a ; a = p . l l e n a L a L i s t a ( ) ; / / Suponemos e s t e mtodo . . . ListaDeCadena b = a ; / / Para no p e r d e r l a cabeza . . . i n t contador = 0; while ( b ! = n u l l ) { b = b . getSiguiente ( ) ; c o n t a d o r ++; }

Vean que ah no perdemos la cabeza (utilizamos a la variable de referencia b para movernos por la lista). El tamao de la lista queda en la variable contador. La propia clase ListaDeCadena ofrece un mtodo para saber cuntos elementos tiene; busca cul es en la documentacin generada de la clase. El while del ejemplo anterior es muy importante, y ser usado mucho con listas. Con un while de ese estilo podemos recorrer toda la lista, o recorrerla hasta encontrar

Estructuras de control y listas

81

algn elemento; y entonces romper el while con un break o con un return. Por supuesto, tambin funciona con un do ... while o con un for. Hemos trabajado con una funcin imaginaria llenaLaLista, pero para crear una lista con un nico elemento solamente tendremos que usar new.
ListaDeCadena a ; a = new ListaDeCadena ( "una cadena" ) ;

Esto crea una lista de la forma:

"una cadena"

null

Noten que el constructor recibe una cadena, que es justamente el elemento de nuestra lista. As construida, la lista que le sigue a a es null. Ahora, como una lista tiene otra lista (la siguiente), tambin podremos construir listas a partir de listas:
ListaDeCadena a ; a = new ListaDeCadena ( "una cadena" ) ; ListaDeCadena b ; b = new ListaDeCadena ( "otra cadena" , a ) ;

Esto quiere decir que tenemos dos constructores; uno recibe una cadena, y el otro recibe una cadena y una lista. El ejemplo de arriba crea una lista de la forma:
b a

"otra cadena"

"una cadena"

null

82 De esta forma, ahora a es el elemento siguiente de b. Noten que de esta manera las listas se construyen al revs; el ltimo elemento en crearse se vuelve la cabeza. Para que no sea as, tendremos la funcin agrega, de la clase ListaDeCadena que har justamente eso, agregar elementos al nal de la lista:
ListaDeCadena a ; a = new ListaDeCadena ( "una cadena" ) ; a . agrega ( "otra cadena" ) ; a . agrega ( "una tercera" ) ; a . agrega ( "y otra" ) ;

Esto ltimo se vera as


a ; "una cadena" "otra cadena" "una tercera" "y otra" null

Utilizaremos las listas mucho a lo largo de sta y las siguientes prcticas.

Paquetes
Hemos estado usando paquetes, aunque sin decir qu son exactamente. Hemos usado los paquetes icc1.interfaz para nuestra consola, e icc1.util para nuestras listas. Adems, generalmente en las prcticas las clases han estado en los paquetes icc1.practica<n>, donde <n> es el nmero de la prctica. Los paquetes son la solucin de Java a problemas relacionados con espacios de nombres (namespaces en ingls). Si por alguna razn quisisemos crear una clase llamada Consola (que no estuviera relacionada para nada con la que hemos estado usando), no podramos hacerlo sin usar paquetes, porque ya existe una clase Consola. Los paquetes nos permiten tener clases con nombres idnticos, siempre y cuando estn en paquetes distintos. Para especicar el paquete de una clase, se utiliza
package mi . paquete ;

al inicio de la clase. La clase Consola, que es del paquete icc1.interfaz, por ejemplo, su nombre calicado es icc1.interfaz.Consola. Igual, el nombre calicado de la clase ListaDeCadena es icc1.util.ListaDeCadena. Ya hemos usado nombres calicados; cuando en el objetivo run de nuestro build.xml especicbamos qu clase iba a ser ejecutada, siempre ponamos el nombre calicado de la clase (recuerden icc1.practica1.UsoReloj). Si no utilizramos la palabra clave import en nuestras clases, tendramos que usar el nombre calicado de la clase en el cdigo. Al usar import icc1. interfaz .Consola;, el

Estructuras de control y listas

83

compilador ya sabe que cuando hacemos referencia a una clase Consola, realmente estamos haciendo referencia a icc1.interfaz.Consola. Los paquetes nos dan entonces una divisin conceptual de nuestras clases. Pero tambin nos dan una divisin fsica: si la clase Consola es del paquete icc1.interfaz, eso quiere decir que, en el disco duro de la computadora donde se compil, el archivo Consola.java est en un directorio llamado interfaz, el cual a su vez est en un directorio icc1. Cuando se han usado clases que ya estaban en algn paquete, stas estaban en un directorio que se llamaban como el paquete. Y todos esos directorios estn debajo del directorio src, que es el que especicamos en el build.xml. Cuando una clase no tqiene un paquete especicado, est en el paquete por omisin. Todas las clases que no tienen paquete especicado estn en el mismo paquete annimo. Hay dos ocasiones cuando no es necesario hacer import de una clase; una es cuando las clases son del paquete java.lang. A las clases de ese paquete no es necesario que les hagamos import. La clase String es de ese paquete, por ejemplo. Adems, no es necesario hacer import nunca de una clase que est en el mismo paquete de la clase con la que estemos trabajando. Por ejemplo, UsoReloj no hace import de VistaReloj ni de Reloj, porque todas estaban en icc1.practica1. A partir de esta prctica, tus clases deben estar en un paquete llamado icc1.practica<n>, donde <n> es el nmero de la prctica.

Ejercicios
1. Nuestra base de datos para agenda tiene una fuerte limitante: es esttica (no puede cambiar de tamao). Para acentuar esto, la variable tabla de la clase es nal; siempre es la misma. No podemos borrar o agregar registros. Las listas parecen un buen candidato para reemplazar a nuestra cadenota (principalmente por el hecho de que no hemos visto ninguna otra cosa). Haz que la clase BaseDeDatosAgenda utilice una ListaDeCadena en lugar de una cadena enorme. Cada registro ser igual que antes, una cadena de tamao TAM_REGISTRO. Queremos que nuestra base de datos tenga los mismos registros de la clase anterior, as que tendrs que crear un constructor para aadir los primeros registros (que puedes copiar de la prctica anterior). Nuestra antigua tabla debe quedar ahora como la lista:
lista ; "Jorge Prez..." "Arturo Lpez..." . . . "Eligio Garca" null

84 Ya tienes el archivo icc1.jar. A estos archivos se les conoce como archivos Jar, o jarles, y sirven para guardar dentro de ellos clases y paquetes. Dentro de icc1.jar ya est la clase ListaDeCadena. Para compilar tu programa, puedes utilizar el mismo objetivo compile de los build.xml anteriores. Igual para ejecutarlo, utiliza el objetivo run que ya hemos usado. La clase ListaDeCadena est en un paquete llamado icc1.util, as que para utilizarla debes poner la siguiente lnea en las clases donde uses listas de cadenas:
import i c c 1 . u t i l . ListaDeCadena ;

Tu clases deben estar en el paquete icc1.practica5. Eso quiere decir que tienen que tener la lnea
package i c c 1 . p r a c t i c a 5 ;

y estar en un directorio llamado icc1/practica5 dentro de tu directorio src. A partir de esta prctica, tus clases deben estar en un paquete llamado icc1.practica<n>, donde <n> es el nmero de la prctica. Aun si utilizas los mismos archivos de prcticas anteriores para hacer tus prcticas, asegrate de que el paquete reeje de qu prctica son. 2. Reescribe las funciones dameRegistroPorNombre y dameRegistroPorTelefono para que funcionen con la nueva implementacin de listas. Vas a necesitar una iteracin, porque ya no podemos utilizar los mtodos de la clase String para buscar (aunque s los vamos a necesitar para comparar cadenas). Prueba que los mtodos funcionen en tu clase de uso. Observa que aunque es mucho el cdigo que hay que escribir, especialmente en la clase BaseDeDatosAgenda, a la clase de uso no hay que hacerle nada. Esto es porque las interfaces del programa (sus mtodos pblicos) no han cambiado. S han cambiado por dentro; pero no han cambiado en nombre, parmetros o tipo de regreso. Por lo tanto para todas las clases que los usaban, es como si no hubieran cambiado. 3. Implementa la funcin pblica agregaRegistro, que recibe una cadena con un registro y regresa void. La funcin debe comprobar que el registro sea vlido (i.e. que tenga longitud TAM_REGISTRO) y, si es as, aadirlo a la base de datos (o sea, a la lista). Comenta la funcin para JavaDoc. Ten en cuenta de que por ahora estamos poniendo dentro del constructor de la clase varios registros; entonces cuando sea llamado este mtodo, la variable de

Estructuras de control y listas

85

clase lista ya no ser vaca. Aun as, tienes que cubrir dentro del mtodo el caso cuando la variable lista sea vaca (o sea, null).

Preguntas
1. Ahora que podemos agregar registros a nuestra base de datos de agenda, qu otras cosas crees que le hagan falta? Justica. 2. Cmo crees que estn implementadas las listas? Explica a detalle. 3. Crees que podras escribir la clase MiListaDeCadena e implementar las funciones getElemento, getSiguiente y agrega? Justica tu respuesta 4. Cmo lo haras? Slo platcalo, pero a detalle.

Prctica: Herencia

A system software crash will cause hardware to act strangely and the programmers will blame the customer engineer. Murphys Laws of Computer Programming #6

Meta
Que el alumno aprenda cmo funciona la herencia en Java.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: comprender y utilizar la herencia en Java; entender qu es la jerarqua de clases; hacer conversin explcita de tipos y profundizar en el uso de interfaces.

88

Desarrollo
La herencia es una de las herramientas ms poderosas de la orientacin a objetos. Nos permite reutilizar trabajo ya hecho, cambiar las interfaces de una clase sin por ello inutilizar otras clases que las usan, resolver varios problemas a la vez, y muchas cosas ms. La herencia es fundamental en el diseo orientado a objetos. Si el lenguaje otorga la facilidad de herencia y no se utiliza, se est desperdiciando gran parte del poder que se obtiene al utilizar un lenguaje orientado a objetos.

Heredar y abstraer clases


Hemos trabajado con la clase BaseDeDatosAgenda, que nos sirve para guardar registros de una agenda. Qu sucede si nos piden ahora una base de datos para una nmina o una librera? Una vez que hayamos terminado la clase BaseDeDatosAgenda nos sera muy sencillo hacer la clase BaseDeDatosLibreria, que hara en esencia exactamente lo mismo que la clase BaseDeDatosAgenda, pero en lugar de usar registros con nombres, direcciones y telfonos, utilizara registros con ttulos, autores y nmeros ISBN. Sin embargo, esto nos llevara a repetir mucho cdigo ya existente, y hemos visto que una de las metas de la orientacin a objetos es justamente reutilizar la mayor cantidad de cdigo posible. La herencia es una de las soluciones para la reutilizacin de cdigo. Cuando uno hereda una clase A en una clase B, los mtodos y variables de clase no privados de la clase A pueden ser usados automticamente por los objetos de la clase B. Los mtodos y variables privados no se heredan; todo lo dems s. Las interfaces slo nos dan el contrato de la clase; qu mtodos deben tener las clases que implementen una interfaz. La herencia nos da mucho ms; nos da variables que ya no hay necesidad de declarar (si no son privadas), y nos da cdigo ya escrito. Con la herencia no slo nos dan el contrato; nos dan cdigo ya escrito. Una vez que la clase B se haya heredado de la clase A, puede denir mtodos y variables de clase propios adicionales a los que se encuentran en A. Entonces los objetos de la clase B podrn hacer uso de los mtodos y variables de clase de la clase A, y adems los que se denan en la clase B. Por eso tambin se dice que heredar una clase es extenderla, y ambos trminos sern equivalentes en estas prcticas. Adems de denir mtodos propios, la clase B puede redenir o sobrecargar los mtodos de la clase A. Veremos un poco ms de eso ms adelante. Si suponemos que tenemos una clase Vehiculo, y quisisemos extenderla con la clase

Herencia
Bicicleta, entonces slo tenemos que hacer public class B i c i c l e t a extends V e h i c u l o { ...

89

Casi todas las clases de Java se pueden extender (en un momento veremos cules no). Podemos extender nuestra clase Matriz2x2 o nuestra clase Consola, por ejemplo. Sin embargo, habr veces que escribiremos una clase con el nico propsito de extenderla. En otras palabras, en ocasiones escribiremos clases de las cuales no pensamos construir objetos, sino heredarlas para construir objetos de las clases herederas. A estas clases las llamaremos abstractas, ya que no se concretarn en objetos. Para que una clase sea abstracta, slo tenemos que agregar abstract a la declaracin de la clase:
public a b s t r a c t class V e h i c u l o { ...

Cualquier clase puede ser abstracta; la palabra clave abstract slo hace que sea imposible construir objetos de esa clase. Sin embargo, cuando hagamos clases abstractas ser generalmente porque tendrn mtodos abstractos, mtodos que no tienen denido todava un comportamiento. Si una clase tiene un mtodo abstracto, la clase misma tiene que ser abstracta, de otra forma no va a compilar. Un mtodo abstracto no tiene cuerpo. Por ejemplo, el siguiente mtodo podra estar denido dentro de la clase Vehiculo que acabamos de mostrar
public a b s t r a c t boolean t r a n s p o r t a ( Paquete p ) ;

(No se pongan quisquillosos con la aparicin espontnea de clases como Paquete; esto es un ejemplo sencillo). Noten que si una clase es completamente abstracta, se asemeja muchsimo a una interfaz. Sin embargo, no son iguales; las clases abstractas pueden declarar variables de clase, que heredarn las clases que las extiendan. Ya hemos visto con las interfaces la razn para tener un cuerpo vaco. La clase Vehiculo tiene un mtodo abstracto llamado transporta; esto signica que todas las clases que extiendan a Vehiculo tienen que implementar el mtodo transporta (a menos que sean abstractas tambin). Con esto forzamos el comportamiento de todas las clases herederas de la clase Vehiculo (igual que con las interfaces). Si una clase extiende a Vehiculo (y no es abstracta), entonces podemos garantizar que tiene un mtodo que se llama transporta, que regresa un booleano, y que recibe un objeto de la clase Paquete como parmetro. Dijimos que las clases, aunque sean abstractas, pueden tener cuerpo en sus mtodos, a diferencia de las interfaces. Entonces, por qu no escribimos el cuerpo de transporta? Si todas las clases que extiendan a Vehiculo heredan todos sus mtodos no privados,

90 entonces por qu no de una vez escribimos transporta? La respuesta es que una bicicleta transporta paquetes muy distinto a como lo hace un automvil. La idea de tener un mtodo abstracto (o en una interfaz), es que todas las clases herederas tendrn al mtodo, pero cmo funcionar va a depender de la clase que hereda. El ejemplo de una clase Vehiculo es muy simple, pero sirve para entender este punto. Tenemos toda la gama posible de vehculos (bicicletas, automviles, patines, helicpteros), y todos ellos pueden transportar paquetes; pero cmo lo hacen funciona de manera distinta en todos. Entonces declaramos un mtodo abstracto transporta en la clase abstracta Vehiculo, y dejemos que cada clase que la extienda (Bicicleta, Automovil, Patines, Helicoptero, etc.) la implemente de acuerdo a cmo funcione la clase. En nuestro ejemplo de la clase Bicicleta, como la clase no es abstracta, tiene que implementar al mtodo transporta. Si fuera abstracta podra no hacerlo. Para que la clase Bicicleta implemente el mtodo transporta, debe usar la misma declaracin que en su clase padre (la clase Vehiculo), pero sin el calicador abstract:
public boolean t r a n s p o r t a ( Paquete p ) { / * Aqu implementamos e l mtodo . * / ... }

La declaracin debe ser exactamente igual que la de la clase padre (como en las interfaces); debe llamarse igual el mtodo, debe regresar el mismo tipo y debe tener el mismo nmero de parmetros y con el mismo tipo y orden en que aparecen (el nombre de los parmetros puede cambiar). El acceso al mtodo debe ser el mismo, aunque un cambio est permitido. Veremos con detalle el acceso al heredar un poco ms adelante. Al nombre de un mtodo y a los tipos y orden de los parmetros se le conoce en conjunto como la rma de un mtodo. En el caso de Java, el tipo del valor que regresa el mtodo no se considera como parte de la rma, pues no lo distingue de otro mtodo con el mismo nombre y parmetros. Para que una clase heredera implemente una versin propia de algn mtodo de su clase padre no es absolutamente necesario que el mtodo sea abstracto. A esto se le llama redenir o sobrecargar un mtodo (overloading es el trmino usado en la literatura en ingls). Incluso se puede utilizar el mtodo que queremos sobrecargar dentro del mtodo sobrecargado, usando super:
public void metodo ( ) { ... super . metodo ( ) ; ... }

La referencia super se utiliza cuando queremos hacer referencia a la clase padre. Esto funciona porque los objetos de una clase heredera pueden comportarse como objetos

Herencia

91 de la clase padre. Hay que recordar eso siempre. Si tenemos una clase y no queremos que sus herederas puedan sobrecargar algn mtodo, entonces podemos denir al mtodo como nal:
public f i n a l void metodo ( ) { ... }

Habamos dejado pendaiente la explicacin de qu era un mtodo nal desde la prctica 4. El signicado es algo distinto a las variables nales; est nicamente relacionado con la herencia. Si un mtodo es nal, ya no puede redenirse o sobrecargarse: ser el mismo para todas las clases herederas. En cierto sentido, el mtodo no puede cambiar el valor que se le dio al denirlo. Dijimos un poco ms arriba que casi todas las clases podan extenderse. Podemos evitar que una clase sea heredada si la hacemos nal:
public f i n a l class ClaseNoHeredable { ...

Funciona de la misma manera que con los mtodos nales; si una clase es nal, sencillamente no puede ser heredada o extendida. Hay que tener en cuenta un ltimo aspecto al heredar una clase: los constructores nunca se heredan. Un constructor no puede heredarse porque un constructor dene cierto estado inicial y ste siempre es concreto. Por lo mismo, no tiene sentido un constructor abstracto. Sin embargo, s podemos llamar a los constructores de nuestra clase padre dentro de los constructores de la clase heredera, utilizando super:
super ( ) ; super ( a , b , c ) ; / / Llamamos a un c o n s t r u c t o r s i n parmetros . / / Llamamos a un c o n s t r u c t o r con parmetros .

Por supuesto, los parmetros de super deben coincidir con los parmetros de algn constructor de la clase padre. Slo podemos usar super para llamar a los constructores de la clase padre dentro de los constructores de la clase heredera.

El acceso en la herencia
Java provee un acceso menos restrictivo que private, pero no tan abierto como public para clases que heredan a otras clases. El acceso se llama protegido (protected). Permite restringir el acceso a mtodos y variables a slo miembros de la familia, o sea, nicamente a clases que hereden.

92 Esto es muy til ya que para clases que no sean herederas el acceso es para motivos prcticos privado, preservando el encapsulamiento de datos y la modularidad de componentes. Adems, permite a clases emparentadas compartir variables y mtodos que no queremos mostrar a nadie ms. Cuando uno sobrecarga un mtodo, o implementa un mtodo abstracto, debe preservar el acceso tal y como lo dena la clase padre. La nica excepcin a esta regla, es cuando el mtodo tiene acceso protegido. Si el mtodo tiene acceso protegido en la clase padre, la clase que extiende puede cambiarle el acceso a pblico; pero es el nico cambio permitido. Al revs es ilegal; no se puede transformar un mtodo pblico en uno protegido. Y todas las otras combinaciones tambin son ilegales.

La jerarqua de clases
En nuestro pequeo ejemplo con la clase Vehiculo hicimos lo que se conoce como una jerarqua de herencia. La clase Vehiculo es una superclase de varias clases; esto suele representarse como en la gura 6.1.
Figura 6.1 Jerarqua de clases

Vehiculo Bicicleta Automovil Patines Helicoptero

La jerarqua de clases tiene una gran importancia en Java; ordena a las clases en trminos de comportamiento y caractersticas. La idea fundamental es que todas las clases de Java estn relacionadas en esta jerarqua, que toda clase sea heredera de alguna otra clase. Para garantizar esto, Java tiene una clase fundamental que se llama Object. La clase Object es La Superclase (con maysculas). Todas las clases de Java son herederas de la clase Object en algn grado (lo que quiere decir que si no son herederas directas, su clase padre s lo es, o si no la clase padre de la clase padre, etc.). Si una clase no extiende explcitamente a alguna otra clase, por omisin extiende a la clase Object. La clase Vehiculo es heredera directa de la clase Object por ejemplo.

Herencia

93 Una de las principales ventajas que ofrece la herencia es que un objeto de una clase heredera garantiza que se puede comportar como un objeto de su clase padre. Un objeto de la clase Bicicleta se puede comportar al menos igual que un objeto de la clase Vehiculo, porque tiene que implementar sus mtodos abstractos, y porque hereda todos los mtodos no privados (que son los que denen el comportamiento). Esto permite programar como en el siguiente ejemplo (es una clase de uso):
public s t a t i c void main ( S t r i n g [ ] args ) { Vehiculo v ; v = i n s t a n c i a ( obtenTipo ( ) ) ; / / Obtenemos a l g n t i p o . . . i f ( v != null ) { Paquete p ; p = obtenPaquete ( ) ; / / Obtenemos un paquete . . . v . transporta (p ) ; } } public s t a t i c V e h i c u l o i n s t a n c i a ( i n t t i p o ) { switch ( t i p o ) { case TODOS: r e t u r n new H e l i c o p t e r o ( ) ; case AUTOMOVIL : r e t u r n new A u t o m o v i l ( ) ; case BICICLETA : r e t u r n new B i c i c l e t a ( ) ; case PATINES : r e t u r n new P a t i n e s ( ) ; default : return null ; } }

Lo que ocurre en el ejemplo es que construimos un objeto de alguna clase heredera de la clase Vehiculo, y transportamos con l algn paquete. No sabemos de qu clase es el objeto v en el mtodo main (en el mtodo instancia lo construimos como Helicoptero, Automovil, Bicicleta o Patines, dependiendo del parmetro que recibamos); pero sabemos que tiene un mtodo transporta, y lo usamos. Piensen en los enteros ( int ) y los enteros cortos (short). Podemos utilizar un entero corto para inicializar un entero, porque un entero se puede comportar como un entero corto. No es tanto que un entero de 16 bits quepa en uno de 32 bits; el hecho importante es que un entero puede emular el comportamiento de un entero corto porque el conjunto de valores que abarcan los enteros contiene al conjunto de valores que abarcan

94 los enteros cortos. De la misma manera, el conjunto de objetos y mtodos que abarca la clase Bicicleta es de mayor tamao (especicidad) que el de la clase Vehiculo; por tanto puede emular el comportamiento de un objeto estrictamente de la clase Vehiculo. Igual pasa con el resto de las clases que extienden a Vehiculo. Esto es muy til, ya que entonces podemos construir dinmicamente dentro del programa el objeto con la clase que nos convenga, o con la que contemos. En el ejemplo de arriba, si tenemos todas las clases disponibles, entonces construimos un helicptero (porque es el ms rpido). Pero si slo hay disponible una bicicleta, pues ni modo, usamos una bicicleta. En el mtodo main no nos importa de qu clase sea nuestro objeto; sabemos que es de alguna clase heredera de Vehiculo y que por lo tanto tiene un mtodo que transporta paquetes. Slo comprobamos que el objeto no sea null, lo que signicara que no tenemos ningn vehculo disponible. Todas las clases de Java (habidas y por haber) se pueden dibujar en rbol para mostrar la jerarqua de clases completa. Todas estn conectadas en muchos casos slo por la clase Object, pero en otros casos hay relaciones ms fuertes. Todo esto nos lleva a que absolutamente todos los objetos de Java pueden comportarse como objetos de la clase Object y por lo tanto utilizar sus mtodos.

Actividad 6.1 Con el navegador de tu preferencia, consulta la documentacin de la clase Object. Los mtodos de Object son mtodos a los que todas las clases de Java tienen acceso (aunque no siempre estn denidos como quisiramos).

Los once mtodos de la clase Object tienen un signicado especial cuando usamos Java. En particular, el mtodo toString es el que utiliza Java para obtener la representacin como cadena de un objeto. La clase Object ofrece el mtodo toString, por lo que todos los objetos de Java pueden llamarlo. En la clase Object el mtodo toString est implementado de tal forma que regresa el nombre de la clase concatenado con una "@", concatenado con un entero que Java utiliza internamente para identicar unvocamente al objeto (pueden pensar que es la direccin de memoria donde vive el objeto, pero no siempre es as). Por supuesto, podemos sobrecargar el mtodo. En la clase Matriz2x2, por ejemplo, lo sobrecargamos para que pudiramos pasar los objetos de la clase como cadenas al mtodo imprime de la clase Consola. Como todos los objetos de Java se pueden comportar como objetos de la clase Object, podemos hacer ahora una clase Lista cuyos elementos no sean cadenas, sino objetos de la clase Object. Con esto automticamente ya tenemos listas que pueden contener objetos de todas las clases de Java (las nativas o las que denamos).

Herencia Actividad 6.2 Con cualquier explorador consulta la documentacin de la clase Lista. Observa que para todo motivo prctico funciona de manera idntica a la clase ListaDeCadena, nada ms que recibe y regresa objetos de la clase Object en lugar de cadenas. La clase Lista tambin est en el archivo Jar icc1.jar, as que no tienes que bajar un nuevo archivo, pero s tienes que importar a la clase usando import icc1. util . Lista ; .

95

La clase Lista puede tener como elementos objetos de cualquier clase. Esto en particular signica que puede tener como elemento un objeto de la clase Lista. O sea, podemos tener listas de listas. Y listas de listas de listas. Se puede complicar tanto como queramos.

Tipos bsicos como objetos


Los tipos bsicos de Java (byte, short, int , long, oat , double, char y boolean) no son objetos; por lo tanto no forman parte de la jerarqua de herencia de Java. Como hacemos una lista de enteros entonces? Para incluir a sus tipos bsicos dentro de la jerarqua de clases, Java ofrece las clases Byte, Short, Integer, Long, Float, Double, Character y Boolean, que son clases que encapsulan o envuelven (wrapping) a los tipos bsicos. Para crear un objeto de la clase Integer que represente el valor de 5, utilizamos
Integer i ; i = new I n t e g e r ( 5 ) ;

El objeto i ahora puede ser agregado a una lista:


Lista l ; l = new L i s t a ( i ) ;

Para obtener el valor entero de este objeto, utilizamos el mtodo intValue de la clase Integer:
int j = i . intValue ( ) ;

Debe ser claro que la funcin equivalente para la clase Float es oatValue. Estas clases no slo envuelven a los tipos bsicos de Java para que sean tratados como objetos; tambin ofrecen funciones que resultan tiles para el tipo bsico al que representan y variables que nos dan informacin sobre el tipo (como MAX_VALUE de la clase

96
Integer, que contiene un valor de tipo int que corresponde al entero de mayor tamao

que podemos representar con 32 bits).

Actividad 6.3 Consulta la documentacin de las clases Byte, Short, Integer, Long, Float, Double, Character y Boolean.

Conversin explcita de tipos


Un poco ms arriba vimos que le pasbamos un objeto de la clase Integer al constructor de la clase Lista.
Lista l i s t a ; l i s t a = new L i s t a ( i ) ;

El constructor de la clase Lista recibe un objeto de la clase Object. Ya vimos que esto es vlido porque al ser Integer una clase heredera de la clase Object, entonces puede comportarse como un objeto de la misma. La fuerte tipicacin de Java no se rompe cuando vamos de una clase ms particular a una ms general. Sin embargo, al revs no funciona. Cuando queramos recuperar el objeto de la clase Integer de la lista, no podremos hacer
Integer k ; k = l i s t a . dameElemento ( ) ;

Esto no va a funcionar porque dameElemento regresa un elemento de la clase Object, y no podemos garantizar que funcione como un objeto de la clase Integer. La clase Integer es ms particular que la clase Object, y un objeto del que solamente se sabe que es de la clase Object no puede garantizar tener todos los mtodos y caractersticas de un objeto de la clase Integer. Pero nosotros s sabemos que es de la clase Integer. Para poder hacer una conversin explcita de tipos (o casting en la jerga computacional), precedemos a la expresin que deseamos convertir con el tipo que queremos entre parntesis:
Integer k ; k = ( I n t e g e r ) l i s t a . dameElemento ( ) ;

Herencia

97

Con esto le ponemos mscara de Integer al objeto de la clase Object. En el ejemplo que estamos manejando va a funcionar porque nosotros sabemos de qu tipo es el objeto que nos regresa la lista; pero si le hacemos una conversin a un objeto que no es del tipo al que estamos convirtiendo, la JVM marcar un error y terminar la ejecucin del programa. Como vimos hace algunas prcticas, la conversin explcita de datos no slo funciona con objetos; si queremos asignarle a un int el valor de un oat hacemos
f l o a t f = 1 . 2F ; int i = ( int ) f ;

Habr veces en que no sabremos con tanta seguridad de qu clase es un objeto. Por suerte, los objetos siempre recuerdan de qu tipo son (se conocen a s mismos) y Java ofrece un operador para comprobar la clase de una referencia; instanceof, un operador binario no conmutativo cuyo operando izquierdo es una variable de referencia y el derecho el nombre de una clase:
Consola c ; c = new Consola ( "Transporte de paquetes" ) ; i f ( v instanceof B i c i c l e t a ) { c . i m p r i m e l n ( "Usamos una bicicleta para transportar." ) ; } else i f ( v instanceof A u t o m o v i l ) { c . i m p r i m e l n ( "Usamos un automvil para transportar." ) ; } ...

Estamos usando nuevamente nuestro ejemplo de la clase Vehiculo. Como pueden sospechar, el operador instanceof regresa true si el objeto del lado izquierdo es ejemplar de la clase del lado derecho, y false en cualquier otro caso. Si tienen dudas de cundo hacer una conversin no se preocupen; el compilador no les va a permitir compilar si la conversin es necesaria. Pero una manera sencilla de plantearlo es imaginar el rbol de la jerarqua de clases; si van de abajo hacia arriba, no es necesaria la conversin explcita. Si van de arriba hacia abajo, s es necesaria.

Interfaces
Qu pasa si queremos una clase que combine el comportamiento de otras dos o tres clases? Lo lgico sera pensar en heredar todas las clases que queramos en nuestra clase. A esto se le llama herencia mltiple. Sin embargo, Java no soporta herencia mltiple. Lo que Java ofrece a cambio son interfaces. Las interfaces son clases que no pueden implementar mtodos; slo pueden

98 declararlos. Una interfaz es de esta forma


public i n t e r f a c e E j e r c i t a d o r { public void quemaCalorias ( i n t kg ) ; }

El acceso de la interfaz tiene que ser pblico; no se admite ningn otro. Lo mismo ocurre con los mtodos y las variables de clase. Y por supuesto, lo ms importante es que los mtodos no tienen cuerpo. Hemos trabajado con interfaces desde el inicio de las prcticas, pero vamos a recapitular lo que sabemos de ellas, y a extender esta informacin un poco ms. No podemos construir objetos de una interfaz. Las interfaces sirven para denir el comportamiento de una clase hacia el usuario, sin implementar ninguno de sus mtodos. Pero la principal ventaja es que una clase puede heredar varias interfaces al mismo tiempo. Pusimos heredar (entre comillas) porque no es exactamente herencia, y de hecho tiene un trmino propio: implementar. Una clase puede implementar tantas interfaces como quiera, adems de que puede extender a alguna otra clase (a lo ms una). Si quisiramos una clase que extendiera a la clase Vehiculo e implementara a la clase Ejercitador, haramos:
public class B i c i c l e t a extends V e h i c u l o implements E j e r c i t a d o r { ...

Si la clase Bicicleta est declarada as, entonces debe implementar los mtodos abstractos de la clase Vehiculo, y adems tiene que implementar todos los mtodos declarados en la interfaz Ejercitador. Las interfaces slo pueden tener variables de clase que sean pblicas y nales. Todas las variables de clase de una interfaz son estticas; incluso aunque no usemos static . El compilador las compila como si fueran estticas de cualquier forma. Tiene esto sentido ya que una interfaz nunca va a tener objetos, entonces una variable no esttica no tiene sentido. Una clase puede implementar tantas interfaces como quiera:
public class Z implements A , B , C, D, E { ...

Las interfaces son la alternativa de Java a la herencia mltiple. Se puede hacer conversin de tipos hacia una interfaz, y pueden ser recibidas como parmetros y regresadas por mtodos. Java en particular utiliza mucho las interfaces para sealar que una clase es capaz de hacer ciertas cosas.

Herencia

99

La herencia en el diseo
La herencia modica signicativamente el cmo disearemos la solucin a un problema. Bsicamente tendremos dos modos de hacer diseo teniendo a la herencia en mente: 1. Disear desde el principio una jerarqua de herencia con clases abstractas e interfaces, extendindolas e implementndolas de manera que resuelvan el problema. 2. Disear una solucin sin herencia y, a la mitad, darnos cuenta de que ciertas clases comparten varias caractersticas y que podemos crear una o varias clases abstractas que al heredarlas nos darn una jerarqua de clases que nos resolver el problema. Por supuesto, la idea es que en algn punto ustedes sean capaces de disear un programa utilizando el primer mtodo, aunque no es sencillo. Lo natural es utilizar el segundo mtodo. Lo usual ser casi siempre que al estar a la mitad de su anlisis se percaten de que hay posibilidad de utilizar la herencia en ciertas partes del problema, y entonces tendrn que ver qu tanto conviene hacerlo. Casi siempre convendr. El primer mtodo requiere tener ya experiencia programando, para que seamos capaces de detectar patrones de problemas, muchos de los cuales tendrn una metodologa para resolverlos que involucrar herencia. No es sencillo llegar a ese nivel; e incluso habiendo llegado a l, la mayora de las veces ocurrir que a la mitad del camino descubramos una nueva manera de utilizar la herencia en nuestro problema. El diseo es la parte ms importante cuando resolvemos un problema. Sin embargo, no es algo intocable; la principal directiva del diseo orientado a objetos es que una vez completada gran parte de la implementacin de nuestro problema, habr que regresar a ver qu partes del diseo estn mal y cambiarlas de acuerdo a ello. Habr incluso ocasiones en que todo un problema tendr que redisearse desde el principio (y en esas ocasiones lo mejor es detectarlo lo ms pronto que se pueda). Por supuesto, hay que disear tratando de evitar que eso ocurra. Pero si el introducir la herencia a algn diseo va a facilitarnos mucho la vida, aun a costa de redisear casi todo de nuevo, no hay que dudar y hay que introducir la herencia. Casi siempre valdr la pena.

Ejercicios
Como ya sabemos utilizar la herencia, es hora de redisear nuestra base de datos para aprovecharla. Ya que vamos a redisear, veamos qu otras cosas estn mal en

100 nuestra base de datos: Tenemos que cambiar de utilizar la clase ListaDeCadena a utilizar la clase Lista, para que dejemos de representar a nuestros registros como cadenas. Ya que podemos meter cualquier clase en nuestras listas, vamos a utilizar una clase para representar a nuestros registros. Se te va a proporcionar un nuevo archivo BaseDeDatosAgenda.java, que extiende a la clase abstracta BaseDeDatos (cuyo archivo tambin se te proporcionar). Tambin se te dar la clase abstracta Registro. La idea es que en la clase BaseDeDatos denimos los mtodos agregaRegistro y quitaRegistro, ya que todas las bases de datos del universo los usan y no tiene sentido andarlos repitiendo. Es tarea tuya entender cmo funcionan los mtodos. En la clase abstracta Registro declaramos dos mtodos; equals y toString. Ambos son sobrecargas (o redeniciones) de los mismos mtodos en la clase Object; por eso hacemos que Registro sea una clase abstracta y no una interfaz. Si fuera una interfaz, las clases que implementaran a Registro no estaran obligadas a implementar equals y toString, porque ya estn implementadas en Object. Slo se te darn los archivos necesarios. Recuerda que t tienes que escribir tu propio build .xml, y hacer tus directorios. 1. Implementa la clase RegistroAgenda, que la requiere la clase BaseDeDatosAgenda. Debe extender a la clase Registro, implementar los mtodos equals, toString y los que t creas necesarios, y adems declarar las variables de clase que representarn el nombre, la direccin y el telfono. El telfono no debe ser cadena y todas las variables de clase debern tener acceso privado. El mtodo equals debe primero ver que el objeto que recibe no sea null. Si lo es, regresa false. Si no es null, debe ver que el objeto sea ejemplar de la clase RegistroAgenda. Si no lo es, regresa false. Si es ejemplar de RegistroAgenda, entonces debe comprobar que el nombre, la direccin y el telfono de this (el objeto que llama al mtodo) sean iguales que las del objeto que recibe como parmetro. Si lo son, regresa true; si no, regresa false. Recuerda: las cadenas se comparan lexicogrcamente. El mtodo toString debe armar una cadena que represente de forma agradable (lo que t entiendas por agradable) al registro y regresar la cadena. Debers tambin denir mtodos para tener acceso y cambiar las variables de clase (las primeras acostmbrate a ponerles prejo get, y a las segunda set). Bsate en la clase Matriz2x2, si quieres. La clase tendr dos constructores; uno que recibir el nombre, la direccin y el telfono (el telfono que reciba no debe ser de tipo cadena). El segundo constructor no recibe parmetros y pone todas las variables de clase en null o 0 (veremos para qu queremos este constructor en la siguiente prctica).

Herencia

101 Todos los mtodos y constructores que se te piden deben tener acceso pblico. Si creas mtodos auxiliares, hazlos privados (no es necesario hacer mtodos privados para resolver el ejercicio). 2. Redene los mtodos dameRegistroPorNombre y dameRegistroPorTelefono en la clase BaseDeDatosAgenda, para que se adapten a nuestro nuevo diseo (con herencia y listas de objetos). Los mtodos conservan el nombre y los parmetros; pero ahora deben regresar un RegistroAgenda. Date cuenta de que ya no es necesario hacer agregaRegistro porque est denido en nuestra clase padre. 3. Reescribe la clase UsoBaseDeDatosAgenda para que construya un objeto de la clase BaseDeDatosAgenda, lea 3 registros que el usuario introducir por el teclado, y haga una bsqueda por nombre que el usuario tambin dar por teclado. Deber imprimirse en la consola si el nombre buscado se encontr o no. Para leer del teclado volveremos a usar la clase Consola. Si quieres leer una cadena, slo tienes que hacer
Consola c ; c = new Consola ( "Base de datos" ) ; S t r i n g nombre ; nombre = c . l e e S t r i n g ( "Dame una cadena:" ) ;

Esto abre una ventana de dilogo donde el usuario puede escribir su cadena. Con el auxilio de tu ayudante, consulta la documentacin de la clase Consola y consulta todos los mtodos lee. 4. Todas las clases y mtodos que implementes deben estar comentados para la documentacin de JavaDoc. Debe quedar claro en la documentacin qu devuelve una funcin o para qu es cada parmetro.

Preguntas
1. Las funciones agregaRegistro y quitaRegistro implementadas en el archivo que corresponde a la clase BaseDeDatos utilizan slo conceptos que hemos visto en sta y prcticas anteriores. Hay algo que no entiendas? Elabora detalladamente tu respuesta (sea armativa o negatica).

102 2. Qu te parece el diseo que est tomando la base de datos? Crees que hay algn error en l? Explica. 3. Te habrs dado cuenta de que para crear una base de datos de una librera (o de lo que fuera), casi slo se necesitara extender la clase Registro a una nueva clase. Cmo lo haras t? Explica a detalle.

Prctica: Entrada/salida y arreglos

Any given program, when running, is obsolete. Murphys Laws of Computer Programming #7

Meta
Que el alumno aprenda cmo funcionan la entrada/salida y los arreglos en Java.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender lo que es entrada y salida; entender lo que es un ujo (stream);

104 utilizar las clases del paquete icc1.es para escribir y leer datos en disco y entender y utilizar arreglos.

Desarrollo
Entrada y Salida (E/S)
Un programa (a menos que sea puramente terico) recibe una cierta entrada. Nuestros programas hasta ahora han recibido su entrada de distintas maneras. Al inicio utilizbamos datos estticos. Si queramos buscar el nombre Juan Prez en nuestra base de datos, tenamos que compilarlo estticamente en la funcin main
public s t a t i c void main ( S t r i n g [ ] args ) { BaseDeDatos bd ; bd = new BaseDeDatosAgenda ( ) ; ... / / Llenamos l a base de datos .

Consola c ; c = new Consola ( "Base de datos de Agenda" ) ; Registro r ; r = bd . dameRegistroPorNombre ( "Juan Prez" ) ; i f ( r == n u l l ) { c . i m p r i m e l n ( "El nombre \"Juan Prez\" no existe." ) ; } else { c . imprimeln ( r ) ; } }

El siguiente paso fue leerlo por teclado, hacindolo dinmico. Para esto, utilizamos las funciones leeXXX de la clase Consola.
public s t a t i c void main ( S t r i n g [ ] args ) { BaseDeDatos bd ; bd = new BaseDeDatosAgenda ( ) ; ... / / Llenamos l a base de datos . Contina en la siguiente pgina

Entrada/salida y arreglos

105
Contina de la pgina anterior

Consola c ; c = new Consola ( "Base de datos de Agenda" ) ; S t r i n g nombre = l e e S t r i n g ( "Nombre a buscar:" ) ; Registro r ; r = bd . dameRegistroPorNombre ( nombre ) ; i f ( r == n u l l ) { c . i m p r i m e l n ( "El nombre \""+nombre+"\" no existe." ) ; } else { c . imprimeln ( r ) ; }

Para la salida hemos utilizado hasta ahora las funciones imprime e imprimeln de la clase Consola. La entrada y la salida son aspectos fundamentales de la programacin, y la mayor parte de los problemas que uno encuentra en el momento de hacer un programa medianamente complejo, tienen que ver con la comunicacin entre el usuario y la computadora (teclado/monitor); con la comunicacin entre el programa y algn dispositivo externo (disco, cinta, CD-ROM), o con la comunicacin entre el programa y algn otro programa, posiblemente en otra mquina (red). En esta prctica cubriremos la entrada y la salida con ms detalle de lo que lo hemos hecho hasta ahora.

Flujos
Por detrs de los telones, la clase Consola utiliza cadenas para comunicarse con el usuario; las funciones imprime e imprimeln transforman cualquier tipo que se les pase en cadena y lo imprimen; y las funciones leeXXX en realidad leen una cadena que luego se trata de convertir al tipo deseado (por ejemplo leeInteger utiliza la funcin esttica parseInt de la clase Integer).

Actividad 7.1 Consulta la documentacin de las funciones parseByte, parseShort, parseInt, parseLong, parseFloat y parseDouble, que estn en las clases Byte, Short, Integer, Long, Float y Double respectivamente.

Esto es porque la clase Consola est diseada para crear una comunicacin con el usuario sencilla y rpida. Pero la mayor parte de la comunicacin que hay en un

106 programa (esto es, su entrada y salida), es sencillamente el transporte de bytes de un lugar para otro. As sea leer datos de un archivo, escuchar un puerto de la red, o esperar entrada a travs del teclado, todo se reduce a una serie de bytes uno detrs del otro. A esta serie de bytes se le suele denominar ujo (stream en ingls), haciendo referencia a un ujo de agua. Hay ujos de los que podemos determinar fcilmente dnde empiezan y dnde terminan (como los archivos en un disco duro o un CD-ROM) y hay ujos que no tienen un principio o un n determinados (como las estaciones de radio en Internet, que mandan bytes ininterrumpidamente con la seal de la estacin). Vindolos como ujos, hay ujos de entrada, es decir, de los que recibimos bytes, y ujos de salida, o sea a los que les mandamos bytes. Un archivo en un CD-ROM es un ujo de entrada (no podemos mandarle informacin, slo obtenerla). Cuando guardamos un archivo en XEmacs, utilizamos un ujo de salida al mandar informacin al disco duro. Pine o GNUS o en general cualquier lector de correo electrnico, utiliza ujos de entrada para recibir correos de su servidor y ujos de salida para mandarle al servidor de correo. Todos los ujos funcionan de la siguiente manera: Se crea el ujo (se abre el archivo en disco, se hace la conexin en red, etc.) Si el ujo es de entrada, se lee de l. Si el ujo es de salida, se escribe en l. Al terminar, se cierra el ujo. Esto es importante, ya que generalmente un ujo utiliza un recurso del sistema que es necesario liberar (por ejemplo, casi todos los sistemas operativos tienen un nmero mximo de archivos que pueden tener abiertos al mismo tiempo). Hay ujos que pueden abrirse simultneamente para lectura y escritura. Sin embargo, esto no necesariamente representa una gran ventaja y no veremos ese tipo de ujos en la prctica. 1. 2. 3. 4.

Filtros
Podemos conectar los ujos con programas o funciones, de tal manera que se reciban bytes por un ujo de entrada, el programa o funcin le haga algo a estos bytes, y se vuelvan a enviar a algn otro lado utilizando un ujo de salida. A estos programas o funciones se les denomina ltros. Un ltro muy comn, por ejemplo, es uno que tome los bytes de entrada y le aplique algn algoritmo que los comprima para mandarlos como salida. Otro ltro puede descomprimir los bytes. Un tercero puede utilizar cifrado de datos para proteger la informacin; un cuarto puede descifrar esos datos para poder leer la informacin. Si subimos el nivel de abstraccin, y ocultamos con ello el hecho de que trabajamos en el fondo con bytes, todo programa es un ltro. Todo programa recibe cierta entrada que en el fondo son bytes, pero que nosotros abstraemos en un nivel ms alto

Entrada/salida y arreglos

107

utilizando para ello el lenguaje de programacin. Manejamos esos bytes como cadenas, enteros, otantes, clases. El programa manipula de cierta manera la entrada, y nos regresa una salida que, de nuevo, en el fondo son bytes, pero que con ayuda del lenguaje manipulamos como entidades menos crudas que los bytes. En el ejemplo que hemos utilizado, la entrada es un nombre, que es una cadena, y la salida es un registro, que es una clase, que tambin podemos representar como cadena gracias al mtodo toString. Java es de los lenguajes de programacin que existen ms poderosos para el manejo de entrada y salida. Tiene un conjunto de clases enorme y facilidades de abstraccin de datos que nos permiten manejar la entrada y la salida en un nivel muy alto de abstraccin. Pero tambin permite la manipulacin directa de bytes desde su ms bajo nivel hasta conversin de clases en bytes y viceversa. Las clases que se encargan de manejar ujos en Java estn en el paquete java.io.

Actividad 7.2 Consulta la documentacin del paquete java.io.

Si Java provee la capacidad de manipular la entrada y la salida en un nivel alto de abstraccin, para qu bajarnos de nivel manipulando bytes directamente? La respuesta es que conforme vamos subiendo de nivel, generalmente a cambio perdemos un poco de velocidad1 y de control. Subir de nivel siempre es mejor porque ayuda al encapsulamiento de datos y a la modularidad de un programa. Tambin hace ms fcil leer y mantener el cdigo. Sin embargo, no siempre podremos hacer eso, o a veces nos convendr ms trabajar directamente en bajo nivel.

Manejo de archivos
Trabajar con bytes es tedioso; un trabajo de bajo nivel. Un byte puede representar cualquier cosa; puede ser una variable del tipo byte de Java, o parte del tipo char, que utiliza dos bytes, que a su vez puede ser parte de una cadena, como tambin puede ser parte de un oat , que utiliza cuatro bytes. Cuando se trabaja a bajo nivel se pueden hacer muchas cosas, pero en general son ms difciles de hacer y hay que tener ms cuidado. Si queremos guardar cadenas en un ujo de salida (un archivo en disco duro por ejemplo), tenemos que convertirlas a bytes y entonces mandarla por el ujo. Si queremos guardar un int , tenemos que hacer lo mismo. Y si luego queremos utilizar un ujo de entrada para leer los datos, tenemos que hacer el camino a la inversa.
Con computadoras cada vez ms veloces, y compiladores cada vez ms inteligentes, esto es cada vez menos perceptible.
1

108 Lo que nosotros quisiramos, son clases a las que les dijramos guarda esta cadena, y que despus slo necesitramos decirles dame la cadena que guard. Tales clases existen, y estn en el paquete icc1.es, y se llaman Entrada y Salida. Son abstractas, para que otras clases concretas implementen las funciones de acuerdo al dispositivo que se use. En particular, las clases EntradaDeDisco y SalidaADisco son para escribir y leer datos en disco duro.

Actividad 7.3 Consulta la documentacin de las clases del paquete icc1.es, especialmente la de las clases EntradaDeDisco y SalidaADisco. El paquete icc1.es est en el mismo archivo Jar, icc1.jar.

Las clases EntradaDeDisco y SalidaADisco estn hechas pensando en facilidad de uso antes que cualquier otra cosa. La clase SalidaADisco escribe sobre archivos de texto. Si uno escribe
SalidaADisco sad ; sad = new SalidaADisco ( "archivo.txt" ) ; sad . e s c r i b e I n t e g e r sad . e s c r i b e F l o a t sad . e s c r i b e S t r i n g sad . e s cr ib eB oo le a n sad . c i e r r a ( ) ; (12); (1.4F ) ; ( "hola mundo" ) ; ( true ) ;

obtendr un archivo de texto llamado archivo.txt que puede ser modicado usando cualquier editor como XEmacs. Pueden comprobarlo haciendo lo siguiente
# cat archivo.txt 12 1.4 hola mundo true #

Los mtodos que leen de la clase EntradaDeDisco, leen una lnea del archivo que abren y tratan de convertirla al tipo solicitado. Para leer el contenido del archivo archivo.txt slo habr que hacer

Entrada/salida y arreglos

109

EntradaDeDisco edd ; edd = new EntradaDeDisco ( "archivo.txt" ) ; int float String boolean i f s b = = = = edd . l e e I n t e g e r edd . l e e F l o a t edd . l e e S t r i n g edd . leeBoolean (); (); (); ();

edd . c i e r r a ( ) ;

Como pueden ver, el orden importa: si lo primero que escriben en un ujo es un


double, asegrense de que un double sea lo primero que lean de ese ujo.

Las clases son muy sencillas de usar; cualquier error grave que ocurra (que no haya permisos para leer o escribir un archivo, por ejemplo), resultar en que el programa termine con un mensaje de error.

Actividad 7.4 Aun cuando las clases del paquete icc1.es son de alto nivel, el uso de entrada y salida siempre es complicado. Para comprender mejor su funcionamiento, baja el archivo ejemploes.tar.gz, complalo y ejectalo: # # # # tar zxvf ejemploes.tar.gz cd es/ ant compile ant run

Examina el cdigo de la clase UsoEntradaSalidaDeDisco para que veas cmo funcionan las clases EntradaDeDisco y SalidaADisco.

Arreglos
Cuando hablamos de ujos, dijimos que se trataban de sucesiones de bytes, uno detrs del otro. Las listas son sucesiones de objetos. Sin embargo, si slo tenemos el primer elemento de una lista, o sea la cabeza, no podemos saber directamente, sin recorrer la lista, dnde est el n-simo. En Java hay otra estructura que es una sucesin de objetos o de tipos bsicos de Java, pero con la ventaja de que estn ocupando posiciones contiguas en la memoria. Como ocupan posiciones contiguas, si sabemos dnde est el primero podemos saber

110 dnde est el segundo, y el tercero, y el n-simo. Podemos tener acceso directo a los objetos o tipos bsicos. Esta estructura son los arreglos. Las listas son una estructura de tamao arbitrario. De entrada no sabemos cuntos elementos en total tiene una lista. Esto es dinmico y muy provechoso; pero tiene la gran desventaja de que al momento de buscar un elemento, tenemos que recorrer gran parte de la lista para encontrarlo. En el peor de los casos (cuando el elemento que buscamos est al nal de la lista) la recorremos toda. A veces sabemos que vamos a necesitar a lo ms k elementos y que en la mayora de los casos vamos a alcanzar este nmero de registros. Para estos casos son los arreglos. Los arreglos son sucesiones de objetos o tipos bsicos de Java, ordenados uno despus de otro, y a los que podemos tener acceso de forma directa gracias justamente a que estn en posiciones contiguas en memoria,algo que los elementos de una lista no pueden garantizar. Para declarar un arreglo (por ejemplo de enteros), hacemos
int [ ] arreglo ;

Los arreglos son objetos calicados de Java. Heredan a la clase Object y tienen acceso a todos los mtodos de la clase. Ya que son objetos, hay que construirlos para poder usarlos. Los arreglos se construyen as:
a r r e g l o = new i n t [ 5 ] ;

Aqu construimos el arreglo para que tenga 5 elementos; pero el nmero de elementos puede ser arbitrariamente grande. Dentro de los corchetes en la construccin podemos poner cualquier expresin de tipo int ; pero si es un entero negativo, la JVM terminar la ejecucin del programa al tratar de construir el arreglo. Una vez denido cuntos elementos tendr un arreglo, no podemos hacer que tenga ms (contrario a las listas). Podemos crear otro arreglo ms grande y copiar los elementos que el primero tena, pero no podemos agrandar un arreglo (o achicarlo). Para tener acceso al n-simo elemento de un arreglo, slo ponemos el nombre del arreglo y entre corchetes el ndice del elemento que queramos. Por ejemplo, para tener acceso al tercer elemento del arreglo de enteros que declaramos arriba hacemos:
arreglo [ 2 ] ;

se es el tercer elemento, ya que comenzamos a contar desde el cero. Por tanto, los elementos de un arreglo tienen ndices que van desde 0 hasta el nmero de elementos menos uno. El elemento arreglo [2] es una variable de tipo int y podemos utilizarla como cualquier otra variable de tipo int :

Entrada/salida y arreglos

111

a r r e g l o [ 2 ] = 15; c . imprimeln ( arreglo [ 2 ] ) ;

Cuando consrtruimos al arreglo con new, el arreglo tiene todos sus elementos sin inicializar, pero Java les asigna 0 por omisin para los arreglos con elementos numricos. Si fuera un arreglo de objetos, los inicializara con null; si fuera de booleanos con false, etc. Podemos pasar arreglos como parmetros:
public void promedio ( i n t [ ] a r r e g l o ) { ...

y regresarlos tambin:
public i n t [ ] g e t A r r e g l o ( ) { ...

En estos casos necesitamos saber el tamao del arreglo que nos pasan o que nos regresan. Todos los arreglos (recuerden que son objetos) tienen una variable pblica y nal llamada length que nos dice cuntos elementos tiene el arreglo:
i n t tam = a r r e g l o . l e n g t h ; c . i m p r i m e l n ( "El arreglo tiene "+tam+" elementos." ) ;

La variable es nal para que no podamos cambiar su valor. No slo podemos hacer arreglos de tipos bsicos; podemos hacer arreglos de cualquier tipo de Java, incluyendo por supuesto cualquier clase:
RegistroAgenda [ ] r e g i s t r o s ; r e g i s t r o s = new RegistroAgenda [ 1 0 0 0 ] ;

Es importante recordar que registros [0] , registros [1] , registros [2] , . . . , registros [999] son referencias inicializadas con null, o sea, registros [0] es igual a null, registros [1] es igual a null, etc. Cuando construimos con new un arreglo, si es de objetos hay que construir a pie cada uno de los elementos del arreglo:
r e g i s t r o s [ 0 ] = new RegistroAgenda ( "Jos Arcadio Buenda" , "Macondo" , 00000001); Contin en la siguiente pgina

112
Contina de la pgina anterior r e g i s t r o s [ 1 ] = new RegistroAgenda ( "{}rsula Buenda" , "Macondo" , 00000002); ... r e g i s t r o s [ 9 9 9 ] = new RegistroAgenda ( "Remedios Buenda" , "Macondo" , 00000999);

La variable registros (sin corchetes) es una variable de referencia a un arreglo de objetos de la clase RegistroAgenda, y el recolector de basura sigue el mismo criterio de las dems referencias en Java para liberar la memoria que ocupa.

Varias dimensiones
A veces una simple sucesin de objetos no nos basta. Por ejemplo, si queremos representar matrices, necesitamos arreglos de dos dimensiones. Para esto hacemos
int [ ] [ ] theMatrix ; t h e M a t r i x = new i n t [ 7 ] [ 5 ] ;

Con esto creamos una matriz de 7 por 5. De aqu es evidente cmo crear arreglos de tres dimensiones:
i n t [ ] [ ] [ ] theCube ; theCube = new i n t [ 7 ] [ 5 ] [ 6 ] ;

Podemos complicarlo tanto como queramos.

Arreglos al vuelo
Si queremos un arreglo con los cinco primeros nmeros primos, tendramos que hacer
i n t [ ] primos ; primos = new i n t [ 5 ] ; primos [ 0 ] primos [ 1 ] primos [ 2 ] primos [ 3 ] primos [ 4 ] = = = = = 2; 3; 5; 7; 11;

Entrada/salida y arreglos

113

Esto es tedioso. Si al momento de compilar sabemos qu valores iniciales tendr un arreglo, podemos crearlo al vuelo
i n t [ ] primos = { 2 , 3 , 5 , 7 , 11 } ;

con lo que nos ahorramos mucho cdigo. Por supuesto, se puede hacer esto con cualquier tipo de Java.
S t r i n g [ ] cuates = { "Rafa" , "Erick" , "Yazmn" , "Karina" , "Citlali" } ;

Tampoco es necesario que los valores sean literales:


RegistroAgenda ra1 = new RegistroAgenda ( "Jos Arcadio Buenda" , "Macondo" , 00000001); RegistroAgenda ra2 = new RegistroAgenda ( "rsula Buenda" , "Macondo" , 00000002); RegistroAgenda ra3 = new RegistroAgenda ( "Aureliano Buenda" , "Macondo" , 00000003); RegistroAgenda [ ] r e g i s t r o s = { ra1 , ra2 , ra3 } ;

Esta forma es muy cmoda, pero nicamente se puede usar al momento de la declaracin. Si el contenido o tamao de un arreglo slo se puede saber en tiempo de ejecucin, tenemos que utilizar la primera forma de construccin y asignar posteriormente los valores, sean stos de tipos bsicos u objetos2 .

Los argumentos de main


Hemos utilizado la siguiente declaracin para main
public s t a t i c void main ( S t r i n g [ ] args ) { ...

El mtodo main tiene que ser declarado as. Si no es pblico, no es esttico, no se llama main o no recibe un arreglo de cadenas como parmetro, entonces se no es el mtodo main, el punto de entrada a nuestro programa.
El nico caso en que Java permite la asignacin al vuelo de un valor a una variable es con cadenas (String).
2

114 Con arreglos ya podemos entender completamente al mtodo main; es un mtodo pblico (porque tiene que llamarse desde fuera de la clase, lo llama la JVM); es esttico, para que pueda ser invocado sin la construccin de ningn objeto (es el primer mtodo que se llama desde la JVM: de dnde podramos sacar un objeto para llamarlo?); no regresa nada (su tipo de regreso es void; ningn otro es permitido); por supuesto se llama main y recibe un arreglo de cadenas como parmetro. Por qu se le pasa un arreglo de cadenas a main? La razn es que muchas veces requerimos que un programa trabaje a partir de ciertos datos. Por ejemplo, si queremos pasarle argumentos a UsoBaseDeDatosAgenda, hacemos lo siguiente con el objetivo run de nuestro build.xml:
1 2 3 4 5 6 7 8 9 10 11 12 < t a r g e t name="run" depends="compile"> < j a v a classname="icc1.practica7.UsoBaseDeDatosAgenda" f o r k ="true"> <arg v a l u e ="12.5" / > <arg v a l u e ="123" / > <arg v a l u e ="Hola mundo" / > <classpath> <pathelement path="build" / > <pathelement path="icc1.jar" / > < / classpath> < / java> </ target>

Qu estamos haciendo aqu? La etiqueta arg (de argument o argumento) nos permite pasarle argumentos al programa. En este caso, "12.5", "123" y "Hola mundo" seran los argumentos del programa. Dentro de main tendramos acceso a ellos con args[0] , args[1] y args[2] respectivamente. Hay que recordar que son cadenas; el primer argumento se interpreta como la cadena "12.5", no como el otante 12.5; y el segundo argumento se interpreta como la cadena "123", no como el entero 123. sta es la nica manera de pasarle parmetros al mtodo main.

Ejercicios
Nuestra base de datos de agenda tiene un grave problema, los registros que damos de alta durante la ejecucin del programa dejan de existir cuando el programa termina. En ese momento desaparecen. Los ejercicios de esta prctica consistirn en que la base de datos de agenda tenga memoria permanente, o sea, que sea capaz de guardar su estado en disco duro.

Entrada/salida y arreglos

115

Para escribir en disco duro utilizaremos la clase SalidaADisco, heredera de la clase Salida. La documentacin de la clase puedes consultarla en la pgina de este manual. Utiliza la clase UsoEntradaSalidaDisco para que veas un ejemplo de cmo utilizar la clase SalidaADisco. 1. En la clase BaseDeDatos implementa el mtodo guardaBaseDeDatos con la siguiente rma:
public void guardaBaseDeDatos ( S a l i d a s a l ) { ...

Como puedes ver, la funcin es concreta. Con esto hacemos que todas las Bases de Datos que extiendan a BaseDeDatos puedan guardar sus registros. En particular, si el mtodo funciona correctamente, nuestra clase BaseDeDatosAgenda podr guardar sus registros sin necesidad de hacerle ninguna modicacin a la clase. El mtodo debe guardar el nmero de registros que hay en la base de datos: la longitud de la lista. Despus debe guardar los registros. Para guardar los registros, supone que la clase Registro tiene un mtodo que se llama guardaRegistro que recibe un objeto heredero de la clase Salida. Gracias a ello, para guardar los registros slo tendremos que recorrer la lista y pedirle a cada registro que se guarde. Si suponemos que tenemos el mtodo guardaRegistro, podemos hacer el mtodo guardaBaseDeDatos en la clase abstracta, ya que no tenemos que saber nada de los registros para pedirles que se guarden. Slo necesitamos saber que se pueden guardar. El objeto heredero de la clase Salida que recibe nuestro mtodo como parmetro es un objeto prestado. No le corresponde al mtodo guardaBaseDeDatos crearlo; lo recibe ya creado. Lo usamos para guardar el nmero de registros, y luego se lo pasamos al mtodo guardaRegistro cuando guardemos cada uno de los registros, pero no lo construimos ni lo cerramos. El crear el objeto y cerrar el ujo se realizar afuera de este mtodo. 2. En la clase Registro declara la funcin abstracta guardaRegistro de la siguiente manera
public a b s t r a c t void g u a r d a R e g i s t r o ( S a l i d a s a l ) ;

Con esto forzamos que todos los objetos de alguna clase heredera de Registro tendrn un mtodo guardaRegistro. El mtodo del primer ejercicio necesita eso.

116 Ahora implementa el mtodo concreto guardaRegistro en la clase RegistroAgenda (tienes que hacerlo; si no lo haces la clase RegistroAgenda dejar de compilar). Igual que el mtodo guardaBaseDeDatos, guardaRegistro recibe prestado el objeto heredero de la clase Salida que le pasamos como parmetro. No crearemos dentro del mtodo al objeto, ni invocaremos su mtodo cierra. Dentro de guardaRegistro slo guardaremos los campos del registro, usando para ello al objeto heredero de la clase Salida. Eso es lo nico que hace el mtodo. 3. Implementa en la clase Registro el mtodo abstracto recuperaRegistro con la siguiente rma:
public a b s t r a c t R e g i s t r o r e c u p e r a R e g i s t r o ( Entrada e n t ) ;

Igual que en el ejercicio anterior, con esto forzamos a todas las clases herederas de Registro a que tengan un mtodo recuperaRegistro para que un registro recupere otro registro del disco duro. Hay que implementar el mtodo recuperaRegistro en la clase RegistroAgenda (si no, no compilar):
public R e g i s t r o r e c u p e r a R e g i s t r o ( Entrada e n t ) { ...

Este mtodo es la razn por la que en la prctica pasada hicimos un constructor sin parmetros a la clase RegistroAgenda. Cuando construyamos un registro sin campos
RegistroAgenda r = new RegistroAgenda ( ) ;

generalmente lo usaremos para leer de disco los campos del registro


RegistroAgenda nuevo ; nuevo = ( RegistroAgenda ) r . r e c u p e r a R e g i s t r o ( edd ) ;

El mtodo recuperaRegistro recibe un objeto heredero de la clase Entrada; al igual que su mtodo hermano guardaRegistro, el objeto es prestado. Ni lo creamos ni lo cerramos dentro de recuperaRegistro. El mtodo recuperaRegistro debe recuperar los campos de un registro de agenda utilizando el objeto de la clase que hereda a Entrada que recibe como parmetro. Asegrate de que respete el orden en que los guard guardaRegistro. Una vez que tenga los campos, debe crear un nuevo registro con ellos, que ser el que regrese el mtodo. Es importante notar que el registro mismo (this, el que llama al mtodo) no se modica de manera alguna. De hecho, el objeto con el

Entrada/salida y arreglos

117

que llamamos a recuperaRegistro slo lo creamos para eso; para recuperar otros registros. 4. Escribe el mtodo recuperaBaseDeDatos en la clase BaseDeDatos con la siguiente rma:
public void recuperaBaseDeDatos ( Entrada ent , Registro r ) { ...

Como su mtodo hermano guardaBaseDeDatos, el objeto heredero de la clase Entrada no se crea ni se cierra dentro del mtodo. Slo lo toma prestado. El mtodo recuperaBaseDeDatos debe leer el nmero de registros (que es lo primero que debe guardar el mtodo guardaBaseDeDatos), y despus con un ciclo leer todos los registros utilizando el mtodo recuperaRegistro, que ser llamado por el objeto r. Cada vez que r llame a recuperaRegistro, regresar un registro nuevo ledo del disco duro que deber ser agregado a la base de datos. Por eso este mtodo recibe un objeto de alguna clase heredera de Registro; para que sepa cmo leer los registros de la base de datos. Este mtodo tiene que ser usado de la siguiente manera:
EntradaDeDisco edd = new EntradaDeDisco ( "agenda.txt" ) ; BaseDeDatosAgenda bdda = new BaseDeDatosAgenda ( ) ; RegistroAgenda r a = new RegistroAgenda ( ) ; bdda . recuperaBaseDeDatos ( edd , r a ) ;

El objeto ra slo lo usamos para que la clase BaseDeDatos sepa cmo leer del disco duro los registros. 5. Has usado los mtodos leeString, leeInteger, leeFloat, etc. para obtener entrada del usuario por el teclado. Sin embargo es incmodo para el usuario tener que estar escribiendo respuestas una por una. Lo ideal sera que pudiera contestar varias preguntas de una vez. Para esto, la clase Consola ofrece las funciones hazPreguntas y dameRespuestaXXX. La primera recibe un arreglo de preguntas
S t r i n g [ ] pregs = { "Nombre:" , "Direccin:" , "Telfono" } ; boolean r e s u l t a d o = c . hazPreguntas ( pregs ) ;

con lo que una ventana de dilogo aparece donde se permite contestar todas las preguntas que estn en el arreglo. La ventana tiene un botn de Aceptar para

118 que se guarden en memoria las respuestas del usuario, y otro botn de Cancelar para cancelar las preguntas. hazPreguntas regresa true si el primer botn es presionado y false si se presiona el segundo o se cierra la ventana. Para obtener las respuestas, se utilizan las funciones dameRespuestaXXX, donde XXX es Integer, String, etc. Si se quiere obtener la primer respuesta como una cadena y la segunda como un entero se hace
S t r i n g resp1 = c . dameRespuestaString ( 0 ) ; i n t resp2 = c . dameRespuestaInt ( 1 ) ;

Es responsabilidad del programador que si se hicieron n preguntas, no se pidan ms de n respuestas. En el archivo UsoEntradaSalidaDisco.java puedes ver un ejemplo de cmo se utilizan los mtodos. Con estos nuevos mtodos, modica UsoBaseDeDatosAgenda para que a) Construya una base de datos de agenda. b) Le pregunte al usuario cuntos registros desea introducir en la base de datos. c) Haga un arreglo de registros del tamao que el usuario introdujo. d) Pida ese nmero de registros con un for, y que cada registro sea pedido usando las funciones hazPreguntas y dameRespuestaXXX. Si el usuario cancela el dilogo (o sea, el mtodo hazPreguntas regresa false), el registro correspondiente en el arreglo debe inicializarse con null. e) Cuando haya terminado de leer todos los registros en el arreglo, con otro for agregue los registros del arreglo en la base de datos. Debe comprobarse que los elementos del arreglo no sean null; si lo son, no deben agregarse en la base de datos. f ) Una vez agregados todos los registros no nulos, debe preguntarle al usuario un nombre de archivo, crear con l un objeto de la clase SalidaADisco, y con ese objeto guardar la base de datos. Hay que cerrar el ujo despus de guardar la base de datos. g) Construya otro objeto de la clase BaseDeDatosAgenda. h) Despus pregunte de nuevo por un nombre de archivo, y utilizar ese nombre para crear un objeto de la clase EntradaDeDisco, y usndolo recuperar la nueva base de datos. Cierre el ujo al acabar. i) Pida un nombre y lo busque en la nueva base de datos recuperada. Por su puesto, todo esto podra hacerse de una manera mucho menos rebuscada, pero queremos

Entrada/salida y arreglos

119

Que prueben todos los mtodos que hagan. Hacerles ms divertida la vida. 6. Sobra decir que todos los mtodos deben tener sus correspondientes comentarios para JavaDoc.

Preguntas
1. En esta prctica, para guardar la base de datos slo guardamos el nmero de registros y despus los registros. Se te ocurre una mejor manera de guardar la base de datos? Justica. 2. Crees que sera mejor si la base de datos utilizara un arreglo en lugar de una lista? Justica.

Prctica: Recursin

Any given program costs more and takes longer. Murphys Laws of Computer Programming #8

Meta
Que el alumno aprenda a usar recursin.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender y utilizar la recursin y usar recursin en arreglos y listas.

122

Desarrollo
Podemos hacer llamadas de funciones dentro de otras funciones. Dentro del mtodo
main en nuestras clases de uso llamamos a varias funciones.

Dentro de esas mismas funciones podemos llamar a otras funciones. El mtodo guardaBaseDeDatos de la clase BaseDeDatos llama al mtodo guardaRegistro de la clase Registro. Si podemos llamar funciones desde funciones, esto lleva a una pregunta casi inmediata; qu pasa si llamamos a una funcin dentro de ella misma? Y la respuesta es la recursin.

Un mal ejemplo: factorial


La funcin factorial es el ejemplo clsico para mostrar la recursin, aunque es un mal ejemplo como veremos ms adelante. La funcin factorial (o n!) est denida para los nmeros naturales (y el cero) de la siguiente manera

El factorial de un nmero n N {0}, que denotaremos como n!, es 1 si n = 0 n (n 1)! en otro caso. Cmo codicamos entonces la funcin factorial? Recursivamente es trivial (asumimos que el int n no es negativo):
int f a c t o r i a l ( int n) { / / Cl u s u l a de escape . i f ( n == 0 ) { return 1; } r e t u r n n * f a c t o r i a l ( n 1);

Lo que hace entonces factorial es llamarse a s misma con un valor distinto cada vez; sa es la idea bsica de la recursin. La comprobacin que aparece en el mtodo debe aparecer en toda funcin recursiva; se le denomina clusula de escape o caso base, y nos permite escapar de la funcin.

Recursin

123 Si una funcin recursiva no tiene clusula de escape o est mal denida, nunca podremos salir de ella. El programa quedar atrapado para siempre en esa funcin, y eso es algo que nunca debe ocurrir. A quedar atrapado en una funcin recursiva (o en una iteracin que nunca acaba) se le llama muchas veces caer en loop o en ciclo innito. Dijimos que la funcin factorial es un mal ejemplo de recursin. Esto es porque podemos hacer factorial iterativamente muy fcil:
int f a c t o r i a l ( int n) { int r ; f o r ( r = 1 ; n > 0 ; n) { r = r *n ; } return r ; }

En Java, una funcin recursiva ser siempre (o en la mayora de los casos) ms cara (en recursos de la computadora) que una funcin no recursiva. La recursin afecta tanto en velocidad como en memoria1 . Sin embargo, el enfoque recursivo es elegante en extremo, y puede simplicar el cdigo de manera signicativa. En el caso de factorial, no tiene sentido hacerla recursiva. La versin iterativa es fcil de entender y no complica el algoritmo. Pero hay algoritmos en los que la versin iterativa es extremadamente compleja, y en estos casos siempre es mejor utilizar la recursin, como veremos enseguida.

Un buen ejemplo: las torres de Hanoi


Tenemos el siguiente problema: tres postes, uno de los cuales tiene cierto nmero de discos de distintos dimetros ordenados de mayor a menor dimetro de abajo hacia arriba (ver la gura 8.1). El reto es relativamente sencillo; tenemos que mover los discos del primer poste al tercero, con la siguiente restriccin: no podemos poner nunca un disco sobre otro de dimetro menor. Cmo movemos los n discos del primer al segundo poste? Recursivamente es sencillo; nuestro caso base (el que nos da la pauta para la clusula de escape), es cuando slo tenemos un disco. En este caso, solamente hay que mover el disco del primer poste al tercero.
Otra vez: el aumento en la velocidad de las computadoras y la existencia de compiladores cada vez ms inteligentes hacen esta diferencia cada vez menos perceptible. Mas la diferencia est ah.
1

124
Figura 8.1 Torres de Hanoi

Ahora slo falta la recursin, que nos da la respuesta de cmo mover n discos de un poste a otro. La recursin es un poco ms complicada, ya que tenemos la restriccin de que no podemos poner un disco sobre otro de menor dimetro. Pero como comenzamos con los discos ordenados, slo hay que encontrar cmo no romper el orden. Esto resulta sencillo ya que tenemos tres postes; entonces para mover n discos del primer poste al segundo poste, primero movemos n 1 discos del primer poste al segundo, ayudndonos con el tercero, movemos el disco ms grande del primer al tercer poste (ya sabemos cmo mover un solo disco), y despus movemos de nuevo los n 1 discos del segundo poste al tercero, ayudndonos con el primero. Y eso resuelve el problema:
* Mueve un d i s c o d e l poste p1 a l poste p2 . */ public void mueveUnDisco ( Poste p1 , Poste p2 ) { / / Movemos e l d i s c o de hasta a r r i b a d e l poste p1 a l poste p2 . } * Pasa n d i s c o s d e l poste p1 a l poste p2 , ayudndose * con e l poste p3 . */ public void mueveDiscos ( Poste p1 , Poste p2 , Poste p3 , i n t n ) { i f ( n == 1 ) { mueveUnDisco ( p1 , p2 ) ; } else { mueveDiscos ( p1 , p3 , p2 , n 1); mueveUnDisco ( p1 , p2 ) ; mueveDiscos ( p3 , p2 , p1 , n 1); } } /* * /* *

Parece mgico, verdad?

Recursin Actividad 8.1 Baja el archivo Jar hanoi.jar de la pgina de las prcticas. El archivo contiene una muestra grca de las Torres de Hanoi. Ejectala con la siguiente lnea de comandos: # java -jar hanoi.jar 5 El parmetro 5 es el nmero de discos a utilizar. Prueba con ms discos si no tienes nada mejor que hacer.

125

Puedes comprobar que el programa de ejemplo utiliza el algoritmo de arriba, paso por paso. La recursin tiene mucho que ver con la induccin matemtica. En la induccin matemtica slo hay que ver un caso base, suponerlo vlido para n = k y demostrarlo para n = k + 1. El mismo principio se aplica aqu; decimos cmo manejar nuestro caso base (la clusula de escape), suponemos que funciona para n 1 y lo hacemos para n.

Para resolver el problema de las torres de Hanoi usamos un algoritmo doblemente recursivo; la funcin que mueve n chas de un poste a otro hace dos llamadas a s misma dentro de la funcin. El algoritmo iterativo de las Torres de Hanoi ocupa varias pginas de cdigo, en comparacin con las menos de quince lneas que utilizamos. Hay una leyenda oriental que dice que si algn da alguien termina de jugar las torres de Hanoi con 64 discos, el Universo dejar de existir. Sin embargo no hay que preocuparse; si se moviera una pieza por segundo, uno tardara ms de quinientos mil millones de aos en terminar de jugar. El problema de las torres de Hanoi es uno de los ejemplos de problemas que tienen solucin, que la solucin puede ser llevada a cabo por una computadora y que sin embargo con una entrada no muy grande (64 en este caso) tardara mucho tiempo en dar el resultado, haciendo la solucin para todo caso prctico intratables.

Listas y recursin
La denicin de factorial y de muchos algoritmos que naturalmente se denen con recursin recuerda en mucho a la de listas. Y las listas son una estructura que se deja manipular muy fcilmente por la recursin. Por ejemplo, para calcular recursivamente cunto mide una lista, necesitaramos algo as:

126

int longitud ( Lista l i s t a ) { i f ( l i s t a == n u l l ) { return 0; } } r e t u r n 1+ l o n g i t u d ( l i s t a . s i g u i e n t e ( ) ) ;

Cuando usemos listas con recursin, generalmente la clusula de escape ser comprobar si la lista es nula. Hasta ahora habamos utilizado iteraciones para tratar con las listas. A partir de ahora, trataremos de usar recursin siempre que sea posible, ya que es la forma natural de tratarlas dada su denicin (recuerda que es una denicin recursiva, en trminos de listas). Adems, har ms legible nuestro cdigo y nos dar ms herramientas al momento de atacar problemas. Sin embargo, deber quedar claro que las listas pueden tratarse recursiva o iterativamente.

Arreglos y recursin
As como las listas se manejan naturalmente con recursin, los arreglos se manejan naturalmente con iteracin. Y sin embargo, as como las listas pueden ser manejadas de las dos formas, los arreglos tambin
/ / Suponemos una v a r i a b l e de c l a s e c de l a c l a s e Consola . void i m p r i m e A r r e g l o ( i n t [ ] m i A r r e g l o , i n t i ) { i f ( i >= m i A r r e g l o . l e n g t h ) { return ; } c . i m p r i m e l n ( "Elemento "+ i +": "+ m i A r r e g l o [ i ] ) ; i m p r i m e A r r e g l o ( m i A r r e g l o , ++ i ) ; }

De hecho, habr ocasiones en que tendr ms sentido que en el ejercicio de arriba.

Recursin

127

Ejercicios
1. Cambia las funciones agregaRegistro y quitaRegistro de la clase BaseDeDatos para que funcionen recursivamente. Debes probar que los mtodos funcionen igual que antes en la clase de uso. Es posible que necesites una funcin auxiliar (privada seguramente) para que sa sea sobre la que realmente se haga la recursin. La recursin puede ser engaosa; presta atencin a los parmetros y a los valores de regreso. Y sobre todo no pierdas nunca de vista la clusula de escape. 2. Cambia los mtodos guardaBaseDeDatos y recuperaBaseDeDatos de la clase BaseDeDatos para que funcionen recursivamente. 3. Para que compruebes que tu mtodo quitaRegistro funciona correctamente, en tu clase de uso busca algn registro, y si existe, brralo, y despus vulvelo a buscar. Date cuenta que de nuevo no debemos cambiar los comentarios de JavaDoc; las funciones siguen haciendo lo mismo, aunque ya no lo hacen de la misma forma. Por esto documentamos qu hace una funcin, no cmo.

Preguntas
1. Cmo te parece mejor que funcionan los mtodos de la clase BaseDeDatos, iterativa o recursivamente? Justica en detalle. 2. Si te das cuenta, hemos hasta ahora detenido el diseo de la base de datos, ya slo hemos cambiado cmo se hacen por dentro las cosas. Cmo crees que podramos mejorar el diseo de la Base de Datos?

Prctica: Manejo de excepciones

If a program is useful, it will have to be changed. Murphys Laws of Computer Programming #9

Meta
Que el alumno aprenda a manejar y crear excepciones.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender qu son las excepciones; manejar excepciones y

130 crear sus propias excepciones.

Desarrollo
Cuando en nuestra base de datos buscamos un registro y ste no existe, regresamos null. sta es una prctica muy comn en computacin: regresar un valor especial en caso de error o de que algo extrao haya ocurrido. Valores como null, -1, o false son muy usados para reportar una situacin extraordinaria. Empero, al complicar ms un programa, el nmero de errores que podemos detectar va creciendo, y estar asignando un valor distinto de retorno a cada tipo de error puede resultar complicado (tenemos un montn de nmeros negativos, pero slo un null). Adems, si estamos dentro de una funcin recursiva, es posible que tengamos que escapar de la recursin tan rpido como sea posible, y eso muchas veces signica saltarse de alguna manera la clusula de escape, y descargar la pila de ejecucin del mtodo. Para todo esto surgi la idea de las excepciones.

Uso de excepciones
La idea detrs de las excepciones es justamente eso: situaciones de excepcin en las que el programa debe de alguna manera detenerse a pensar qu est ocurriendo, ver si puede manejar la situacin o, si no hay remedio, morir graciosamente. Un programa nunca debe explotarle en la cara a un usuario. Si un error ocurre debe entonces tratar de encontrar la manera de continuar la ejecucin del programa, o terminar con un mensaje de error claro y conciso. La idea es sencilla: cuando haya mtodos que se prestan a situaciones de excepcin, tendrn la capacidad de lanzar excepciones (throw en ingls). Si existe la posibilidad de que un mtodo usado por el programa lance una o ms excepciones, primero deber intentarse (try en ingls) ejecutar el mtodo. Si la situacin extraordinaria ocurre en el intento, la funcin lanzar una excepcin (slo una), que debe ser atrapada (catch en ingls). Por ltimo (nally), habr cosas que hacer despus de ejecutar la funcin, independientemente de si algo malo ocurri en el intento. Todo esto se logra con los bloques try ... catch ... nally .

try ... catch ... nally


La sintaxis general para usar funciones que lancen excepciones es

Manejo de excepciones

131

try { / / I n v o c a c i n a mtodos p o t e n c i a l m e n t e p e l i g r o s a s . } catch ( AlgunaExcepcion e1 ) { / / Manejo de e r r o r ( e x c e p t i o n h a n d l e r ) . } catch ( OtraExcepcion e2 ) { / / Manejo de e r r o r ( e x c e p t i o n h a n d l e r ) . } catch ( UnaExcepcionMas e3 ) { / / Manejo de e r r o r ( e x c e p t i o n h a n d l e r ) . ... } catch ( UnaUltimaExcepcion eN ) { / / Manejo de e r r o r ( e x c e p t i o n h a n d l e r ) . } finally { / / Limpieza . }

Dentro del bloque try se ejecutan funciones peligrosas. Con peligrosas queremos decir que mientras la funcin se ejecute pueden ocurrir situaciones de excepcin, que dentro de la misma funcin no hay manera de manejar.1 En estas prcticas ya hemos visto clases en las que dentro de las funciones pueden ocurrir muchsimas excepciones; las clases del paquete icc1.es. Cuando hacemos entrada y salida, hay muchos errores potenciales, como por ejemplo: que no tengamos permisos para abrir un archivo; que no podamos conectarnos a una mquina del otro lado del mundo; que el espacio en disco se haya acabado; que se vaya la luz; que desconecten el mdem; que el sistema operativo se trabe; que la computadora explote en llamas, que tiemble la tierra; que el n del mundo nos alcance; . . .

Podemos seguir con muchas otras, pero lo importante es que al efectuar entrada y salida no podemos asegurar ninguna de las operaciones; abrir, escribir/leer, cerrar. Dentro del bloque try envolvemos entonces a las posibles situaciones de excepcin.
Siempre hay manera de manejar errores; una de ellas es terminar el programa con un mensaje de error (que es lo que hacen todas las bibliotecas del curso hasta ahora). Lo que se quiere decir es que no hay manera general correcta de manejarlas.
1

132

try { / * Abrimos e l a r c h i v o . * / BufferedReader i n = n u l l ; i n = new BufferedReader (new F i l e R e a d e r ( "datos.txt" ) ) ; nombre = i n . readLine ( ) ; d i r e c c i o n = i n . readLine ( ) ; S t r i n g tmp = i n . r e a d L i n e ( ) ; t e l e f o n o = I n t e g e r . p a r s e I n t ( tmp ) ; / / Convertimos a e n t e r o . in . close ( ) ; }

Por supuesto, dentro del bloque try puede haber instrucciones inofensivas; la idea es agrupar a un conjunto de instrucciones peligrosas en un solo bloque try (aunque se mezclen instrucciones inofensivas), para no tener que utilizar un try para cada instruccin peligrosa. En el momento en que una de las funciones lance una excepcin, la ejecucin del try se detendr y se pasar a los bloques catch, tambin llamados manejadores de excepcin. Es importante entender que la ejecucin se detendr en el momento en que la excepcin sea lanzada. Esto quiere decir que cuando ejecutemos las lneas
BufferedReader i n = n u l l ; i n = new BufferedReader (new F i l e R e a d e r ( "datos.txt" ) ) ;

si el constructor de BufferedReader lanza una excepcin, entonces in continuar valiendo null en el bloque catch, ya que nunca se realiz la asignacin; la excepcin fue lanzada antes. Si la excepcin es lanzada, la ejecucin pasa entonces a los manejadores de excepcin, los catch, donde es atrapada por el manejador que le corresponda.
catch ( Fil eNo tFo u n d E x c e p t i o n f n f e ) { c . p r i n t l n ( "El archivo \"datos.txt\" no existe." ) ; } catch ( IOException i o e ) { c . p r i n t l n ( "No se pudo leer el archivo." ) ; }

Por ltimo, la ejecucin pasa al bloque nally (si existe), que se ejecuta haya o no habido una excepcin. Es para realizar operaciones crticas, sin importar si hubo o no errores en el bloque try, o el tipo de estos errores, si es que hubo.

Manejo de excepciones

133

finally { terminaTareas ( ) ; }

El cdigo que hemos descrito quedara de la siguiente forma


try { / * Abrimos e l a r c h i v o . * / BufferedReader i n = n u l l ; i n = new BufferedReader (new F i l e R e a d e r ( "datos.txt" ) ) ; nombre = i n . readLine ( ) ; d i r e c c i o n = i n . readLine ( ) ; S t r i n g tmp = i n . r e a d L i n e ( ) ; t e l e f o n o = I n t e g e r . p a r s e I n t ( tmp ) ; / / Convertimos a e n t e r o . in . close ( ) ; } } catch ( Fil eN o t F o u n d E x c e p t i o n f n f e ) { c . p r i n t l n ( "El archivo \"datos.txt\" no existe." ) ; } catch ( IOException i o e ) { c . p r i n t l n ( "No se pudo leer el archivo." ) ; } finally { terminaTareas ( ) ; }

En este ejemplo hay que notar que dentro del try se llevan a cabo todas las operaciones relacionadas con leer de disco. El bloque nally es opcional; no es obligatorio que aparezca. Pero debe quedar en claro que cuando aparece, el bloque nally se ejecuta incondicionalmente, se lance o no se lance ninguna excepcin. La nica manera de impedir que un nally se ejecute es terminar la ejecucin del programa dentro del catch (lo que no tiene mucho sentido si existe un bloque nally ). La parte ms importante al momento de manejar excepciones es, justamente, los manejadores de excepciones. Es ah donde determinamos qu hacer con la excepcin. Para utilizar los manejadores de excepciones, necesitamos utilizar a las clases herederas de Throwable.

Actividad 9.1 Apoyndote en la pgina del curso, consulta la documentacin de la clase Throwable.

134

La clase Throwable
La sintaxis de un manejador de excepcin es
catch ( < AlgunaClaseHerederaDeThrowable > t ) { ... }

donde <AlgunaClaseHerederaDeThrowable> es alguna clase heredera de la clase Throwable (lanzable, en ingls). En particular, las clases Exception y Error son herederas de Throwable. El compilador de Java protesta si en un manejador de excepcin tratamos de utilizar una clase que no sea heredera en algn grado de la clase Throwable. Las excepciones son objetos comunes y corrientes de Java, con mtodos y variables que pueden utilizarse para obtener ms informacin de la situacin extraordinaria que ocurri. Por ejemplo, todos los objetos de clases herederas de Throwable tienen la funcin printStackTrace, que imprime informacin acerca de en qu mtodo ocurri el error y qu otros mtodos fueron llamados antes que l (lo que se conoce como la pila de ejecucin). Si una funcin es capaz de lanzar n tipos de excepciones (o sea, n distintas clases herederas de Throwable), entonces debemos atrapar las n excepciones; el compilador no permitir la compilacin si no lo hacemos. Si debemos atrapar distintas excepciones que son herederas de una clase, podemos slo atrapar su superclase. En particular, el siguiente cdigo es vlido en la gran mayora de los casos (ya que casi todas las excepciones heredan a Exception):
try { / / Cosas p e l i g r o s a s que lanzan c h o r r o c i e n t a s excepciones // distintas . } catch ( E x c e p t i o n e ) { c . i m p r i m e l n ( "Algo malo ocurri." ) . }

Sin embargo es considerada una mala prctica, ya que perdemos todo el no control que nos dan las excepciones (no distinguimos qu excepcin estamos atrapando), y ser prohibido su uso en estas prcticas. Empero, atrapar a la clase Exception estar permitido si es al nal, cuando ya se ha escrito el manejador de todas las excepciones posibles que pueden lanzarse.

Manejo de excepciones

135

Lanzamiento de excepciones
Hay dos maneras de manejar las excepciones dentro de nuestros mtodos. La primera es la que ya vimos, atrapar las excepciones en un bloque try ... catch ... nally . La segunda es encadenar el lanzamiento. Encadenar el lanzamiento signica que asumimos que nuestro mtodo no es el responsable de manejar la excepcin, sino que debe ser responsabilidad de quien sea que llame a nuestro mtodo. Entonces, nuestro mtodo en lugar de atrapar la excepcin volver a lanzarla. Piensen en la situacin de excepcin como en una papa caliente, y en los mtodos como una la de personas. La primera persona (o sea mtodo) que de repente tenga la papa caliente en las manos, tiene la opcin de manejar la papa (resolver el problema, manejar la excepcin), o pasrselo a la persona que sigue, lavndose las manos. La papa (o excepcin) puede entonces ir recorriendo personas (mtodos) en la la (en la pila de ejecucin) hasta que algn mtodo maneje la excepcin en lugar de lanzarla de nuevo. Para lanzar de nuevo la excepcin, tenemos que hacer especco que nuestro mtodo puede lanzar esa excepcin utilizando la clusula throws en el encabezado, por ejemplo:
public void miFuncionLanzadora ( i n t arg ) throws AlgunaExcepcion {

Una vez que nuestro mtodo miFuncionLanzadora declara que puede lanzar a la excepcin AlgunaExcepcion, ya no es necesario que envolvamos con un try a ninguna funcin que pudiera lanzar tambin a la excepcin AlgunaExcepcion. El lanzamiento queda encadenado automticamente. Por supuesto, podemos manejar distintos tipos de papas calientes; nada ms debemos especicarlo en el encabezado
public void miFuncionLanzadora ( i n t arg ) throws AlgunaExcepcion , OtraExcepcion , UnaExcepcionMas {

Un mtodo puede ser capaz de lanzar un nmero arbitrario de excepciones, y cuando sea llamado el mtodo deber ser dentro de un bloque try (a menos que la funcin que llame al mtodo tambin lance las mismas excepciones). Si llamamos a un mtodo que puede lanzar excepciones, y el mtodo desde donde lo llamamos no lanza las mismas excepciones, entonces hay que envolver la llamada con un try; de otra manera, el compilador protestar. El mtodo main puede lanzar excepciones, que son atrapadas por la JVM. Es una forma de probar mtodos que lanzan excepciones sin necesitar escribir los bloques try ... catch ... nally (sencillamente encadenamos main al lanzamiento de las excepciones).

136

Creacin de excepciones
Java provee muchsimas excepciones, destinadas a muchos tipos de problemas que pueden ocurrir en un programa, pero en cuanto un programa comienza a crecer en complejidad ciertos problemas inherentes al programa aparecen. Por ejemplo, mucha gente puede creer que en una base de datos si se trata de agregar un registro idntico a uno ya existente, es una situacin lo sucientemente singular como para lanzar una excepcin. Entonces quisiramos crear excepciones por nuestra cuenta. Para hacerlo, slo tenemos que crear una clase que herede a Exception2
public class R e g i s t r o D u p l i c a d o extends E x c e p t i o n { }

Noten que la denicin de la clase est completa. Generalmente no habr necesidad de crear funciones especiales para las excepciones; slo las utilizaremos para distinguir los errores de nuestros programas, y para ello nos bastarn los mtodos heredados de la clase Exception. Esto no quiere decir que no podamos sobrecargar las funciones de la clase Exception, ni que no sea necesario nunca. Simplemente, en la mayora de los casos podremos ahorrrnoslo. Lo que s muchas veces haremos ser crear una jerarqua de clases exclusivamente para excepciones de nuestro programa. Esto facilitar muchas cosas al momento de denir qu excepciones puede lanzar una clase, o al momento de escribir los bloques try ... catch ... nally . Si queremos que una funcin lance una excepcin creada por nosotros, al igual que cuando lanzaba excepciones creadas por alguien ms, necesitamos que en el encabezado especique que puede lanzar la excepcin, y despus necesitamos crear la excepcin con new, para poder lanzarla con throw.
public void miFuncionLanzadora ( i n t arg ) throws MiExcepcion1 , MiExcepcion2 { ... i f ( algoMalo ) { throw new MiExcepcion1 ( ) ; } ... Contina en la siguiente pgina O cualquier heredero de la clase Throwable, incluso podemos heredar a Throwable directamente, aunque generalmente ser a Exception a la que extendamos.
2

Manejo de excepciones

137
Contina de la pgina anterior i f ( algoPeor ) { throw new MiExcepcion2 ( ) ; } ...

Si las clases MiExcepcion1 y MiExcepcion2 son herederas de la clase MiSuperExcepcion, podemos reducir el cdigo de arriba como sigue:
public void miFuncionLanzadora ( i n t arg ) throws MiSuperExcepcion { ... i f ( algoMalo ) { throw new MiExcepcion1 ( ) ; } ... i f ( algoPeor ) { throw new MiExcepcion2 ( ) ; } ... }

Se darn cuenta de que lanzar las excepciones es algo que se decide en tiempo de ejecucin (por eso los if s). No tiene sentido hacer una funcin que siempre lance una excepcin. Las excepciones son as, singulares, y deben ser lanzadas despus de hacer una evaluacin acerca del estado del programa (como ver que un nuevo registro es idntico a uno ya existente).

El ujo de ejecucin
Al momento de hacer un throw, la ejecucin de la funcin termina, y la excepcin recorre todas las funciones que han sido llamadas, detenindolas, hasta que encuentra un bloque catch que lo atrape. Eso rompe cualquier algoritmo recursivo, cualquier iteracin y en general cualquier ujo de ejecucin, lo que hace al throw mucho ms poderoso que el return. Dado que se pueden descartar muchas llamadas a funciones que se hayan hecho (desmontndolas en la pila de ejecucin), una excepcin en general debe tratar de manejarse ms que de repararse. Repararla requerira que se volvieran a hacer las llamadas detenidas por el lanzamiento de la excepcin, con el posible riesgo de que el error ocurra de nuevo.

138 Lo sensato es dar un mensaje al usuario de que lo que pidi no puede realizarse, y regresar a un punto de ejecucin seguro en el programa (el men inicial o la pantalla de inicio).

La clase RuntimeException
Al inicio de la prctica se mencion, un poco en tono de broma, lo que puede ir mal en el momento de ejecutar un programa. Aunque ciertamente si nos llega el n del mundo lo que menos nos va a importar es si nuestro programa funciona o no, tambin es cierto que hay cosas que ocurren que no tienen que ver con que nuestro programa est bien o mal escrito o con que el usuario pidiera algo con sentido o no. La memoria y el disco se acaban, o fallan, las conexiones se cortan, la luz se va. Pero existen cosas todava ms sencillas que tambin pueden ocurrir y que harn abortar al programa: una divisin por cero, tratar de utilizar una referencia nula, salirnos del rango de un arreglo. Todos estos errores en Java tambin son excepciones, pero no hay necesidad de atraparlas en bloques try ... catch ... nally . Y no hay necesidad de hacerlo porque estos errores ocurren no en el contexto de llamar un mtodo, sino en el contexto de un simple instruccin. Todas estas excepciones son herederas de la mal llamada clase RuntimeException3 , que a su vez es heredera de Exception. El lanzamiento de excepciones herederas de la clase RuntimeException no se debe a situaciones excepcionales, sino a descuidos del programador. Si alguien no quiere que una excepcin creada por l forzosamente tenga que ser atrapada, sencillamente tiene que hacerla heredera de RuntimeException y el compilador dejar que el mtodo sea llamado aun cuando no se trate de atrapar ninguna excepcin. Tampoco es necesario que se especiquen en el encabezado con la clusula throws. Pero se considera un error hacer las excepciones de un programa herederas de la clase RuntimeException, ya que como se dijo no son situaciones excepcionales, sino descuidos del programador. Es frecuente que en partes crticas de cdigo, pueda atraparse una excepcin de la clase RuntimeException al nal de los manejadores de excepciones para asegurar que nada malo ocurra. Sin embargo, casi todas las excepciones herederas de esta clase pueden prevenirse (comprobar que el denominador no sea cero, comprobar que la referencia no sea nula, siempre revisar los lmites de un arreglo), as que es mejor programar cuidadosamente. Sin embargo, mientras se est depurando un programa puede ser muy til atrapar las excepciones de la clase RuntimeException, sobre todo en los casos en que no parece
3

Mal llamada porque todas las excepciones ocurren en tiempo de ejecucin.

Manejo de excepciones

139

haber una razn por la que el programa truena.

El paquete java.io
Hasta ahora, las excepciones de nuestro archivo Jar icc1.jar haban sido suprimidas. Para que se den una idea, las excepciones se manejaban as
try { r = Integer . parseInt ( s ) ; } catch ( NumberFormatException n f e ) { System . e r r . p r i n t l n ( "*** ERROR ***: \""+s+ "\" no es un byte vlido." ) ; System . e r r . p r i n t l n ( "*** ERROR ***: Ejecucin "+ "del programa detenida." ) ; System . e x i t ( 1 ) ; }

El mtodo exit de la clase System (como ya habrn adivinado) obliga a que un programa termine. El manejo de excepciones se suprimi para que no tuvieran la necesidad de preocuparse por ellas y se pudieran concentrar en resolver los problemas que se les planteaban. Mas las excepciones son de las caractersticas que hacen de Java un buen lenguaje de programacin y su uso debe ser fomentado y diseminado. A partir de esta prctica tendrn que lidiar con las excepciones. Recibirn nuevas versiones de los paquetes icc1.interfaz e icc1.util que lanzarn algunas excepciones, aunque todas son herederas de RuntimeException (lo que quiere decir que no es necesario que las envuelvan en un try con su correspondiente catch). Sin embargo, el paquete java.es es especial. La entrada y salida tal y como la maneja Java ustedes deberan ser capaces de manejarla sin muchos problemas; excepto por el uso de excepciones. Como la entrada y salida es una de las fuentes ms amplia de excepciones, el nico motivo para la existencia de java.es era que ustedes no tuvieran que lidiar con ellas. Por lo tanto, el paquete java.es desaparecer; para manejar entrada y salida debern utilizar las clases del paquete java.io, con sus correspondientes excepciones. Deberan ser capaces de entender estas clases consultando slo la documentacin de este paquete. Sin embargo, para facilitarles algo la vida, todo el cdigo fuente de los paquetes que se han visto en el curso, incluyendo los ejemplos, estar disponible para que lo consulten. Entre el cdigo se encuentra un ejemplo de entrada/salida usando las clases de Java; lo necesitarn para que sus clases compilen utilizando las nuevas bibliotecas.

140

Actividad 9.2 De la pgina del manual baja las nuevas bibliotecas que se utilizarn en el curso; el archivo se llama icc1.jar tambin. Tambin baja los archivos .tar.gz relacionados con excepciones, noexcepciones.tar.gz, excepciones.tar.gz y ejemplos.tar.gz. El primero tiene las bibliotecas que utilizamos en el curso, las que no usan excepciones. El segundo tiene el cdigo fuente de las nuevas bibliotecas. El tercero tiene el cdigo fuente de los ejemplos vistos en el curso. En particular, revisa la clase UsoJavaIO, porque muestra cmo utilizar las clases de Java para escribir y leer del disco duro.

Ejercicios
Baja las nuevas versiones de los paquetes que se han utilizado. El nuevo archivo se llama tambin icc1.jar, pero est en otro directorio; asegrate de que sea el correcto, porque tienen que compilar tus clases con las nuevas versiones. Los paquetes y clases se llaman igual; icc1.util.Lista, icc1.interfaz.Consola, etc. Nada ms ya no debes usar el paquete icc1.es, y tampoco la clase icc1.util.ListaDeCadena. Todo el cdigo de todas las clases est disponible para que lo consultes. Comprueba que realmente ests usando la biblioteca correcta; comprueba que el cdigo compile con la que debe. 1. Haz que tu base de datos (tal y como la dejaste la prctica pasada) compile con los nuevos paquetes. Lo ms obvio ser que el compilador se quejar de que no existe ninguna clase del paquete icc1.es. Reemplaza su uso con clases del paquete java.io. Utiliza el ejemplo proporcionado para ver qu tienes que cambiar. Vas a tener que utilizar bloques try ... catch ... nally para manejar las excepciones que lanzan los nuevos paquetes. Puedes hacer lo que creas conveniente en los manejadores de excepcin, pero trata de hacer algo. No siempre se puede; en particular, si algo sale mal al leer o escribir en disco, realmente no hay mucho que puedas hacer. Eso s, contina la ejecucin del programa (no uses el mtodo exit de la clase System). Recuerda: las excepciones son clases. Para utilizar las excepciones de un paquete, debes importar la excepcin al inicio de la clase. Por ejemplo, para atrapar la excepcin FileNotFoundException, del paquete java.io, debes poner al inicio de tu clase

Manejo de excepciones
import j a v a . i o . F i l e N o t F o u n d E x c e p t i o n ;

141

2. En la clase BaseDeDatos, modica las funciones agregaRegistro y borraRegistro para que lancen excepciones (distintas) en los siguientes casos: cuando se trate de agregar un registro duplicado; cuando se trate de borrar un registro que no existe.

Tienes que crear las clases de las excepciones. Llmalas como t quieras y utiliza la jerarqua de clases que consideres necesaria. Una excepcin de la biblioteca de clases de Java que se presenta frecuentemente es la excepcin NullPointerException, que es lanzada cada vez que se trata de usar una referencia nula (null) en un contexto en el que est prohibido. Esta excepcin es heredera de la clase RuntimeException, as que no es obligatorio atraparla. Haz que tus mtodos lancen tambin la excepcin NullPointerException cuando el parmetro que reciban sea null. No es necesario usar ningn import para usar la clase NullPointerException, ya que est en el paquete java.lang. Una vez modicadas las funciones, tendrs que modicar la clase de uso, ya que se necesitar atrapar las excepciones para que compile la clase de nuevo. 3. Cuando modiques las funciones agregaRegistro y borraRegistro, modica los comentarios de JavaDoc utilizando la etiqueta @throws para especicar qu excepciones lanza y cundo las lanza. Tambin comenta las clases de tus excepciones.

Preguntas
1. Crees que es til el manejo de excepciones? Justica a detalle. 2. Qu otras excepciones crees que podran crearse para nuestra base de datos? Explica.

Prctica: Interfaces grcas

10

If a program is useless, it will have to be documented. Murphys Laws of Computer Programming #10

Meta
Que el alumno aprenda a crear interfaces grcas.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender las interfaces grcas en Java; entender qu son los eventos y crear interfaces grcas.

144

Desarrollo
La mayor parte del mundo utiliza la computadora slo como una herramienta. Escriben trabajos en ellas, calculan sus impuestos, organizan su agenda, platican con alguien del otro lado del mundo, le mandan un correo electrnico al hijo que est estudiando lejos, etc. Para hacer todo eso necesitan aplicaciones sencillas de usar, que les permita realizar su trabajo y pasatiempos sin muchas complicaciones. Cuando se dice que estas aplicaciones deben ser sencillas de usar, muchos coinciden en que la mejor manera de facilitarle la vida al usuario es que el programa se comunique con l a travs de interfaces grcas de usuario, IGUs, o GUIs por las mismas siglas en ingls. (El hecho de que las interfaces grcas nos compliquen la vida a los programadores no parece importarle mucho a los usuarios. . . ) A lo largo de estas prcticas hemos utilizado interfaces grcas sencillas, aunque ustedes mismos no las han programado. En esta prctica veremos cmo se programan las interfaces grcas y discutiremos la caracterstica ms importante de ellas, algo que se ha evitado intencionalmente hasta ahora. Nos referimos al manejo de eventos. Las primeras versiones de Java utilizaban las clases del paquete java.awt para crear interfaces grcas. Las ltimas versiones utilizan las clases del paquete javax.swing, que ser el denitivo una vez que las aplicaciones escritas con AWT sean convertidas a Swing. En esta prctica utilizaremos Swing.

Actividad 10.1 Ve las clases del paquete javax.swing y java.awt en la pgina indicada en este manual.

Componentes
Una interfaz grca en Java est compuesta de componentes alojados en un contenedor. Veamos una pequea interfaz grca que se ha usado a lo largo del curso, la pequea ventana de dilogo que aparece cuando hacemos
c . l e e S t r i n g ( "Mete una cadena:" ) ;

La ventana la puedes ver en la gura 10.1

Interfaces grcas Figura 10.1 Dilogo de leeString

145

En esta ventana de dilogo hay cuatro componentes: Un componente de la clase JDialog, un dilogo. ste es un contenedor de primer nivel. Este tipo de componentes es generalmente una ventana, y los ms usuales son JFrame, JDialog y Applet. Un componente de la clase JPanel, un panel. ste es un contenedor intermedio, y lo utilizaremos para agrupar de manera sencilla otros componentes. Un componente de la clase JLabel, una etiqueta. Las etiquetas generalmente sern slo usadas para mostrar algn tipo de texto, aunque pueden mostrar imgenes (pixmaps). Un componente de la clase JTextField, un campo de texto. Son componentes usados casi siempre para obtener del usuario una cadena de pocos carcteres o de una sola lnea. Los componentes en una interfaz grca estn ordenados jerrquicamente. En el primer nivel est el dilogo, dentro de l est el panel, y dentro del panel estn la etiqueta y la caja de texto, como se muestra en la gura 10.2.
Figura 10.2 Jerarqua de componentes

JDialog JPanel JLabel JTextField

El dilogo, que en este sencillo ejemplo es la ventana principal, sirve ms que nada para poner juntos a los dems componentes. Es un contenedor de primer nivel. El panel es un contenedor intermedio. Su propsito en la vida es ayudarnos a acomodar otros componentes. La etiqueta y la caja de texto son componentes atmicos, componentes que no contienen a otros componentes, sino que por s mismos muestran o reciben informacin del usuario.

146 En el pequeo diagrama de la jerarqua de componentes nos saltamos algunos componentes que hay entre el dilogo y el panel. Sin embargo, la mayor parte de las veces no habr que preocuparse de esos componentes. Para crear esa interfaz grca, el cdigo fundamental es
JDialog JPanel JLabel JTextField dialogo panel etiqueta cajaTexto = = = = new new new new JDialog ( . . . ) ; JPanel ( . . . ) ; JLabel ( . . . ) ; JTextField ( . . . ) ;

panel . add ( e t i q u e t a ) ; panel . add ( c a j a T e x t o ) ; d i a l o g . getContentPane ( ) . add ( panel ) ; d i a l o g . pack ( ) ; d i a l o g . s e t V i s i b l e ( true ) ;

Administracin de trazado
Los componentes se dibujan sobre un contenedor intermedio (como son los objetos de la clase JPanel) utilizando un administrador de trazado (layout manager). La administracin de trazado es de las caractersticas ms cmodas de Swing, ya que prcticamente slo hay que especicar cmo queremos que se acomoden los componentes y Swing se encarga de calcular los tamaos y posiciones para que los componentes se vean bien distribuidos y con un tamao agradable. Sin embargo puede volverse un poco complicado el asunto, ya que hay muchos administradores de trazado, y de acuerdo a cul se escoja, nuestra aplicacin terminar vindose muy distinta. A lo largo de la prctica veremos distintos ejemplos de los administradores de trazado ms comunes.

Eventos
Ya sabemos cmo se ponen algunos componentes en una pequea ventana. Ahora, cmo hacemos para que la interfaz reaccione a lo que el usuario haga? Por ejemplo, la caja de texto de nuestro dilogo debe cerrar el dilogo cuando el usuario presione la tecla ENTER en ella.

Interfaces grcas

147

Esto se logra con el manejo de eventos. Un evento ocurre cuando el usuario genera algn cambio en el estado de un componente: teclear en una caja de texto, hacer click con el ratn en un botn, cerrar una ventana. Si existe un escucha (listener en ingls) para el objeto que emiti el evento, entonces podr ejecutar su manejador del evento. Adems, un objeto puede tener ms de un escucha, cada uno de los cuales ejecutar su manejador del evento si el evento ocurre. Entender cmo se emiten los eventos y cmo se manejan es lo ms complicado de hacer interfaces grcas. Lo dems es seguir algunas recetas para utilizar los distintos tipos de componentes, que son muchos, pero que todos funcionan de manera similar.

Escuchas
Los escuchas son interfaces; son como clases vacas que nos dicen qu tipos de eventos se pueden emitir, pero que nos dejan la libertad de implementar qu hacer cuando los eventos sucedan. Hagamos un ejemplo completo: una pequea ventana con una etiqueta para mensajes y un botn. Primero hagmoslo sin escuchas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import import import import import import import import j a v a x . swing . JFrame ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a x . swing . B o r d e r F a c t o r y ; j a v a . awt . BorderLayout ; j a v a . awt . G r i d L a y o u t ; j a v a . awt . C o n t a i n e r ;

public class EjemploBoton { public s t a t i c void main ( S t r i n g [ ] args ) { JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; JPanel panel = new JPanel ( ) ; panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; panel . s e t L a y o u t (new G r i d L a y o u t ( 0 , 1 ) ) ; Contina en la siguiente pgina

148
Contina de la pgina anterior 22 23 24 25 26 27 28 29 30 } 31 } panel . add ( boton ) ; panel . add ( e t i q u e t a ) ; C o n t a i n e r c = frame . getContentPane ( ) ; c . add ( panel , BorderLayout .CENTER ) ; frame . pack ( ) ; frame . s e t V i s i b l e ( t r u e ) ;

Qu hace el programa? En las lneas 14, 15 y 16 crea los tres principales componentes de nuestra aplicacin: nuestra ventana principal (JFrame), nuestro botn (JButton) y nuestra etiqueta (JLabel). Esos son los componentes que el usuario ver directamente. Lo siguiente que hace el programa es crear un panel (lnea 18). El nico propsito del panel es agrupar a nuestro botn y a nuestra etiqueta en la ventana principal. Lo primero que hace es ponerse un borde de 5 pixeles alrededor [20], y denirse un administrador de trazado [21]; en este caso es de la clase GridLayout, lo que signica que ser una cuadrcula. Ya que le pasamos como parmetros 0 y 1, se entiende que tendr un nmero no determinado de renglones (de ah el 0), y una sola columna. Lo ltimo que hace el panel es aadirse el botn y la etiqueta [22,23]. El programa despus obtiene la ventana contenedora de la ventana principal [25] (todos los contenedores de primer nivel tienen una ventana contenedora), y a aqul le aadimos el panel [26]. Para terminar, la ventana principal se empaca y se hace visible. Durante el proceso de empacado se calculan los tamaos de los componentes, para que automticamente se dena el tamao de la ventana principal.

Actividad 10.2 Escribe la clase EjemploBoton.java y escribe el correspondiente build.xml para que puedas compilarla y correr el ejemplo.

El programa no hace nada interesante (ni siquiera termina cuando se cierra la ventana). El botn puede ser presionado cuantas veces queramos; pero nada ocurre porque no le aadimos ningn escucha. Cuando el usuario hace click en el botn se dispara un evento; pero no hay ningn manejador de evento que se encargue de l. Vamos a escribir un escucha para nuestro ejemplo. Ya que el evento que nos interesa es el click del botn, utilizaremos un escucha de ratn, o mouse listener, que es una interfaz que est declarada como sigue

Interfaces grcas

149

1 package j a v a . awt . event ; 2 public i n t e r f a c e MouseListener extends E v e n t L i s t e n e r { 3 public void mouseClicked ( MouseEvent e ) ; 4 public void mouseEntered ( MouseEvent e ) ; 5 public void mouseExited ( MouseEvent e ) ; 6 public void mousePressed ( MouseEvent e ) ; 7 public void mouseReleased ( MouseEvent e ) ; 8 }

(Aunque se pueden escribir escuchas propios, Java provee ya los escuchas ms utilizados para distintos eventos, por lo que no hay necesidad de hacerlos.) Esto nos dice que los escuchas de ratn ofrecen mtodos para cuando el ratn haga click sobre el componente (mouseClicked), para cuando el ratn se posa sobre el componente (mouseEntered), para cuando el ratn deja de estar sobre el componente (mouseExited), para cuando se presiona el botn del ratn sin soltarlo (mousePressed), y para cuando se suelta el botn del ratn despus de presionarlo (mouseRelease). Si queremos hacer un escucha para nuestro botn, debemos crear una clase que implemente la interfaz MouseListener, diciendo qu debe hacerse en cada caso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package i c c 1 . ejemplos ; import j a v a . awt . event . MouseListener ; import j a v a . awt . event . MouseEvent ; import j a v a x . swing . JLabel ; public class MiEscuchaDeRaton implements MouseListener { private i n t contador ; p r i v a t e JLabel e t i q u e t a ; public MiEscuchaDeRaton ( JLabel e t i q u e t a ) { this . etiqueta = etiqueta ; contador = 0; } public void mouseClicked ( MouseEvent e ) { c o n t a d o r ++; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; } public public public public void void void void mouseEntered ( MouseEvent e ) { } mouseExited ( MouseEvent e ) { } mousePressed ( MouseEvent e ) { } mouseReleased ( MouseEvent e ) { }

150

Realmente slo implementamos la funcin mouseClicked; las otras no nos interesan. Pero debemos darles una implementacin a todas (aunque sea como en este caso una implementacin vaca), porque si no el compilador se quejar de que no estamos implementando todas las funciones de la interfaz MouseListener. Qu hace este manejador de evento? Slo se hace cargo del evento mouseClicked; cuando el usuario haga click sobre el botn, la etiqueta cambiar su mensaje a Clicks y el nmero de clicks que se hayan hecho. Para que nuestro botn utilice este escucha slo tenemos que agregarlo (modicando la clase EjemploBoton):
15 16 17 18 19 J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; MiEscuchaDeRaton escucha = new MiEscuchaDeRaton ( e t i q u e t a ) ; boton . addMouseListener ( escucha ) ;

Actividad 10.3 Escribe la clase MiEscuchaDeRaton.java y modica la clase de uso EjemploBoton.java para que el botn utilice el escucha. Compila y ejecuta el programa de nuevo.

Adaptadores
Como ocurri arriba, muchas veces no querremos implementar todas las funciones que ofrece un escucha. La mayor parte de las veces slo nos interesar el evento de click sobre un botn y no nos importar el resto. Para esto estn los adaptadores. Los adaptadores son clases concretas que implementan a las interfaces de un escucha, pero que no hacen nada en sus funciones; de esta forma, uno slo hereda al adaptador y sobrecarga la funcin que le interese. Por ejemplo, el adaptador de ratn (mouse adapter) es
1 package j a v a . awt . event ; 2 public class MouseAdapter implements MouseListener { 3 public void mouseClicked ( MouseEvent e ) { } 4 public void mouseEntered ( MouseEvent e ) { } 5 public void mouseExited ( MouseEvent e ) { } 6 public void mousePressed ( MouseEvent e ) { } 7 public void mouseReleased ( MouseEvent e ) { } 8 }

Interfaces grcas

151

No hacen nada sus funciones; pero si nicamente nos interesa el evento del click del ratn slo hay que implementar mouseClicked. Nuestra clase MiEscuchaDeRaton se reducira a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import j a v a . awt . event . MouseAdapter ; import j a v a . awt . event . MouseEvent ; import j a v a x . swing . JLabel ; public class MiEscuchaDeRaton extends MouseAdapter { private int contador ; p r i v a t e JLabel e t i q u e t a ; public MiEscuchaDeRaton ( JLabel e t i q u e t a ) { this . etiqueta = etiqueta ; contador = 0; } public void mouseClicked ( MouseEvent e ) { c o n t a d o r ++; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; } }

Como los otros eventos no nos interesan, los ignoramos. No hacemos nada si ocurren. Todos los escuchas de Java tienen un adaptador disponible para facilitarnos la vida.1
Actividad 10.4 Vuelve a modicar MiEscuchaDeRaton.java para que extienda el adaptador de ratn. Compila y ejecuta de nuevo el programa.

Clases internas y annimas


En estas prcticas no hemos mencionado que Java puede tener clases internas; clases declaradas dentro de otras clases
public class A { ... Por supuesto, a menos que el escucha slo tenga un mtodo. No tendra sentido tener un adaptador en ese caso.
1

152
class B { ... }

La clase B es clase interna de A; slo puede ser vista dentro de A. Adems, los mtodos de la clase B pueden hacer referencia a las variables de clase de la clase A, incluso a las privadas. Esto es posible ya que la clase B es parte de hecho de la clase A. Las clases internas son muy tiles con los escuchas y adaptadores, ya que nada ms nos interesa que los vean dentro de la clase donde estamos creando nuestra interfaz grca. Cuando compilemos el archivo A.java, que es donde viven las clases A y B, se generarn dos archivos: A.class, y A$B.class; este ltimo quiere decir que B es clase interna de A. Podemos entonces crear nuestros escuchas y adaptadores dentro de la clase donde construyamoss nuestra interfaz grca. Pero aun podemos hacer ms: podemos utilizar clases annimas. Las clases annimas son clases creadas al vuelo, que no tienen nombre. Por ejemplo, para nuestro botn podramos aadirle nuestro escucha de ratn al vuelo, de esta manera
16 17 18 19 20 21 22 23 24 f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; f i n a l i n t contador = { 0 } ; boton . addMouseListener (new MouseAdapter ( ) { public void mouseClicked ( MouseEvent e ) { contador [ 0 ] = contador [ 0 ] + 1 ; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r . i n t V a l u e ( ) + "." ) ; } });

Nuestra clase EjemploBoton.java quedara as


1 2 3 4 5 6 7 package i c c 1 . ejemplos ; import import import import import j a v a x . swing . JFrame ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a x . swing . B o r d e r F a c t o r y ; Contina en la siguiente pgina

Interfaces grcas

153
Contina de la pgina anterior

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

import import import import import

j a v a . awt . BorderLayout ; j a v a . awt . G r i d L a y o u t ; j a v a . awt . C o n t a i n e r ; j a v a . awt . event . MouseAdapter ; j a v a . awt . event . MouseEvent ;

public class EjemploBoton { public s t a t i c void main ( S t r i n g [ ] args ) { JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; f i n a l i n t contador = { 0 } ; boton . addMouseListener (new MouseAdapter ( ) { public void mouseClicked ( MouseEvent e ) { contador [ 0 ] = contador [ 0 ] + 1 ; e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r [ 0 ] + "." ) ; } }); JPanel panel = new JPanel ( ) ; panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; panel . s e t L a y o u t (new G r i d L a y o u t ( 0 , 1 ) ) ; panel . add ( boton ) ; panel . add ( e t i q u e t a ) ; C o n t a i n e r c = frame . getContentPane ( ) ; c . add ( panel , BorderLayout .CENTER ) ; frame . pack ( ) ; frame . s e t V i s i b l e ( t r u e ) ; } }

Actividad 10.5 Vuelve a modicar el archivo EjemploBoton.java para que utilice la clase annima. Complalo y ejectalo de nuevo.

154 Ah estamos creando una clase annima al vuelo al invocar la funcin addMouseListener. Al compilar EjemploBoton.java, la clase annima se compilar en el archivo EjemploBoton$1.class. Con eso nos ahorramos tener que crear una clase completa en su propio archivo, cuando slo nos interesan uno o dos de sus mtodos. Las clases annimas son mucho ms cmodas de utilizar que el tener que crear una clase para cada uno de los eventos de un programa (el nmero de eventos puede aumentar a cientos de ellos). Muchos notarn (y se preguntarn) por qu ahora la etiqueta y el contador son variables nales, y por qu el contador es un arreglo de enteros con un nico elemento. La mayor desventaja de las clases annimas es que no pueden utilizar variables locales declaradas fuera de ellas mismas, a menos que sean nales. En el caso de la etiqueta realmente no importa ya que no modicamos la referencia al objeto (cambiamos su contenido, pero la referencia sigue siendo la misma). En el caso del contador queremos que se modique en cada llamada, y entonces no podemos usar una variable de tipo int , ya que tiene que ser nal. Por eso usamos un arreglo; de nuevo, no modicamos la referencia al arreglo y su tamao (contador), sino un elemento del arreglo (contador[0]). Parte del problema en el ejemplo es que todo el cdigo est en el mtodo main; se puede resolver todo de manera ms elegante si rediseamos un poco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package i c c 1 . ejemplos ; import import import import import import import import import import import import j a v a x . swing . B o r d e r F a c t o r y ; j a v a x . swing . J B u t t o n ; j a v a x . swing . JFrame ; j a v a x . swing . JLabel ; j a v a x . swing . JPanel ; j a v a . awt . BorderLayout ; j a v a . awt . C o n t a i n e r ; j a v a . awt . G r i d L a y o u t ; j a v a . awt . event . MouseAdapter ; j a v a . awt . event . MouseEvent ; j a v a . awt . event . WindowAdapter ; j a v a . awt . event . WindowEvent ;

public class EjemploBoton { private i n t contador ; public EjemploBoton ( ) { contador = 0; Contina en la siguiente pgina

Interfaces grcas

155
Contina de la pgina anterior

22 JFrame frame = new JFrame ( "Ejemplo de Botn" ) ; 23 J B u t t o n boton = new J B u t t o n ( "Haz click" ) ; 24 f i n a l JLabel e t i q u e t a = new JLabel ( "Esto es una etiqueta" ) ; 25 26 frame . addWindowListener (new WindowAdapter ( ) { 27 public void windowClosing ( WindowEvent evento ) { 28 System . e x i t ( 0 ) ; 29 } 30 }); 31 32 boton . addMouseListener (new MouseAdapter ( ) { 33 public void mouseClicked ( MouseEvent e ) { 34 c o n t a d o r ++; 35 e t i q u e t a . s e t T e x t ( "Clicks: "+ c o n t a d o r +"." ) ; 36 } 37 }); 38 39 JPanel panel = new JPanel ( ) ; 40 panel . s e t B o r d e r ( B o r d e r F a c t o r y . createEmptyBorder ( 5 , 5 , 5 , 5 ) ) ; 41 panel . s e t L a y o u t (new G r i d L a y o u t ( 0 , 1 ) ) ; 42 43 panel . add ( boton ) ; 44 panel . add ( e t i q u e t a ) ; 45 46 C o n t a i n e r c = frame . getContentPane ( ) ; 47 c . add ( panel , BorderLayout .CENTER ) ; 48 49 frame . pack ( ) ; 50 frame . s e t V i s i b l e ( t r u e ) ; 51 } 52 53 public s t a t i c void main ( S t r i n g [ ] args ) { 54 EjemploBoton e = new EjemploBoton ( ) ; 55 } 56 }

(Aadimos tambin un escucha a la ventana, en las lneas 2630, para que cuando el usuario la cierre, el programa termine). Las clases internas y annimas fueron pensadas mayormente para interfaces grcas, pero podemos usarlas cundo y dnde queramos. Sin embargo nosotros slo las utilizaremos para interfaces grcas (y de hecho es lo recomendable).

156 En los ejercicios de la prctica podrs utilizar clases normales, internas o annimas, dependiendo de cmo lo preeras.

Los componentes de Swing


Swing ofrece varios componentes (herederos casi todos de la clase JComponent) para crear prcticamente cualquier tipo de interfaz grca. Por motivos de espacio veremos slo una breve descripcin de cada uno de los componentes que Swing ofrece, pero se darn descripciones detalladas en el ejemplo completo que se ver ms adelante, y slo para los componentes que se utilicen o que se relacionen con lo que se utilice.

Actividad 10.6 Consulta la documentacin de la clase JComponent en la pgina referida para ello.

Contenedores de primer nivel


Los contenedores de primer nivel son ventanas (excepto los que corresponden a
Applet), la raz en nuestra jerarqua de componentes. Todos tienen un contenedor (heredero de la clase Container), llamada ventana contenedora o content pane. Adems de

la ventana contenedora, todos los componentes de primer nivel tienen la posibilidad de que se les agregue una barra de men. Los contenedores de primer nivel son: JFrame. (Marco) Son ventanas autnomas y que no dependen de ninguna otra ventana. Generalmente sern la ventana principal de nuestras aplicaciones. JDialog. (Dilogo) Son ventanas ms limitadas que los objetos de la clase JFrame. Dependen de otra ventana, que es la ventana padre (parent window) del dilogo. Si la ventana padre es minimizada, sus dilogos tambin lo hacen. Si la ventana padre es cerrada, se cierran tambin los dilogos. Applet. (No tiene sentido en espaol) Emulan a una ventana para correr aplicaciones de Java dentro de un navegador de la WWW. No los veremos. Para manejar los eventos de un contenedor de primer nivel, se utiliza un escucha de ventana o window listener.

Interfaces grcas Actividad 10.7 Consulta en la pgina correspondiente la documentacin de las clases JFrame, JDialog, WindowListener y WindowAdapter. Tambin consulta la documentacin de las clases JOptionPane y JFileChooser. La clase Applet no se utilizar en estas prcticas.

157

Contenedores intermedios
Los contenedores intermedios nos sirven para agrupar y presentar de cierta manera distintos componentes. Mientras que se pueden realizar varias cosas con ellos, aqu slo daremos una breve descripcin de cada uno de ellos y nos concentraremos en los objetos de la clase JPanel en el ejemplo ms abajo. Panel. Es el contenedor intermedio ms exible y frecuentemente usado. Est implementado en la clase JPanel; los pneles no tienen en s mismos casi ninguna funcionalidad nueva a la que heredan de JComponent. Se usan frecuentemente para agrupar componentes. Un panel puede usar cualquier administrador de trazado, y se le puede denir fcilmente un borde. Ventana corrediza. Provee barras de desplazamiento alrededor de un componente grande o que puede aumentar mucho de tamao. Se implementa en la clase JScrollPane. Ventana dividida. Muestra dos componentes en un espacio jo determinado, dejando al usuario el ajustar la cantidad de espacio dedicada a cada uno de los componentes. Se implementa en la clase JSplitPane. Ventana de carpeta. Contiene mltiples componentes, pero slo muestra uno a la vez. El usuario puede cambiar fcilmente entre componentes. Se implementa en la clase JTabbedPane. Barra de herramientas. Contiene un grupo de componentes (usualmente botones) en un rengln o en una columna, permitindole al usuario el arrastrar la barra de herramientas en diferentes posiciones. Se implementa en la clase JToolBar. Hay otros tres contenedores intermedios, ms especializados: Marco interno. Es como un marco y tiene casi los mismos mtodos, pero debe aparecer dentro de otra ventana. Se implementa en la clase JInternalFrame. Ventana en capas. Provee una tercera dimensin, profundidad, para acomodar componentes. Se debe especicar la posicin y tamao para cada componente. Se implementa en la clase LayeredPane.

158 Ventana raz. Provee soporte detrs de los telones para los contenedores de primer nivel. Se implementa en la clase JRootPane.

Componentes atmicos
Los componentes atmicos sirven para presentar y/o recibir informacin del usuario. Son los eventos generados por estos componentes los que ms nos interesarn al crear nuestras interfaces grcas. Los siguientes son componentes que sirven para recibir informacin del usuario: Botn, Botn de dos estados, Radio botn. Proveen botones de uso simple. Los botones normales estn implementados en la clase JButton. Los botones de dos estados son botones que cambian de estado cuando se les hace click. Como su nombre lo indica, tienen dos estados: activado y desactivado. Estn implementados en la clase JCheckBox. Los botones de radio son un grupo de botones de dos estados en los cuales slo uno de ellos puede estar activado a la vez. Estn implementados en la clase JRadioButton. Caja de combinaciones. Son botones que al hacer click en ellos ofrecen un men de opciones. Estn implementados en la clase JComboBox. Listas. Muestran un grupo de elementos que el usuario puede escoger. Estn implementadas en la clase JList. Mens. Permiten hacer mens. Estn implementados en las clase JMenuBar, JMenu y JMenuItem. Rangos. Permiten escoger un valor numrico que est en cierto rango. Estn implementados en la clase JSlider. Campo de texto. Permiten al usuario escribir una sola lnea de texto. Estn implementados en la clase JTextField. Los siguientes son componentes que slo muestran informacin al usuario, sin recibir ninguna entrada de l: Etiquetas. Muestran texto, un icono o ambos. Implementadas en la clase JLabel. Barras de progreso. Muestran el progreso de una tarea. Implementadas en la clase JProgressBar.

Interfaces grcas

159

Pistas. Muestran una pequea ventana con informacin de algn otro componente. Implementadas en la clase JToolTip. Los siguientes son componentes que presentan informacin con formato, y una manera de editar esa informacin: Selector de color. Una interfaz para seleccionar colores. Implementada en la clase JColorChooser. Selector de archivos. Una interfaz para seleccionar archivos. Implementada en la clase JFileChooser. Tabla. Un componente exible que muestra informacin en un formato de cuadrcula. Implementada en la clase JTable. Soporte para texto. Una serie de componentes para manejo de texto, a distintos niveles y con distintos grados de complejidad. Est en las clases JTextArea, JTextComponent, JTextField, y JTextPane. rbol. Un componente que muestra datos de manera jerrquica. Implementado en la clase JTree.

Un ejemplo paso a paso


Como se dijo al inicio de la prctica, lo ms difcil de las interfaces grcas es el manejo de eventos. Lo dems es aprender a utilizar los distintos componentes de Swing. Para que tengan una referencia concreta, veremos un ejemplo completo donde haremos un editor de texto sencillo.
Figura 10.3 Jerarqua de componentes
JFrame JMenuBar Men: Archivo JMenuItem: Nuevo archivo JMenuItem: Abrir archivo JMenuItem: Guardar archivo JMenuItem: Guardar archivo como... JMenuItem: Quitar programa Men: Ayuda JMenuItem: Acerca de... JPanel JTextArea JLabel

160

Diseo de un editor de texto sencillo


Queremos hacer un editor de texto sencillo en una clase llamada Editor. Podr cargar archivos de texto del disco duro, editarlos y salvarlos de nuevo. La mayor parte de la interfaz grca ser un componente que nos permita mostrar y editar texto. Adems, nuestra interfaz deber tener un men de archivo con opciones para abrir, guardar, salir y crear un nuevo archivo, y otro men de ayuda con informacin del programa. Tambin deberemos tener una etiqueta que le diga al usuario si el archivo actual ha sido o no modicado. El componente que nos permite mostrar y editar texto es un objeto de la clase JTextArea; nuestra ventana principal es un objeto de la clase JFrame. Para acomodar los componentes usaremos un objeto de la clase JPanel. Ya con esto, nuestra jerarqua de componentes sera como se ve en la gura 10.3.

Variables de clase y clases importadas


Usaremos componentes y eventos de Swing, componentes y eventos de AWT, y algunas clases del paquete java.io, para leer y guardar nuestros archivos en disco duro. Tambin importamos la clase Locale, para que el programa hable bien espaol. Veremos algo ms de esto ms adelante. Nuestro paquete ser icc1.ejemplos, como con todos los ejemplos que hemos estado usando; entonces nuestra clase quedara as al inicio:
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package i c c 1 . ejemplos ; import import import import import import import import import import import import j a v a x . swing . JFrame ; j a v a x . swing . JTextArea ; j a v a x . swing . JPanel ; j a v a x . swing . J S c r o l l P a n e ; j a v a x . swing . JLabel ; j a v a x . swing . JMenuBar ; j a v a x . swing . JMenu ; j a v a x . swing . JMenuItem ; j a v a x . swing . J F i l e C h o o s e r ; j a v a x . swing . JOptionPane ; j a v a x . swing . BoxLayout ; j a v a x . swing . KeyStroke ; // // // // // // // // // // // // Ventana p r i n c i p a l . rea de t e x t o . Panel . Ventana c o r r e d i z a . Etiqueta . Barra de men . Men . Elemento de men . S e l e c c i o n a d o r de a r c h i v o s . . Di l o g o s . Trazado en c a j a . Teclazo .

import j a v a x . swing . event . DocumentListener ; / / Escucha en e l / / rea de t e x t o . import j a v a x . swing . event . DocumentEvent ; / / Evento en e l / / rea de t e x t o . Contina en la siguiente pgina

Interfaces grcas

161
Contina de la pgina anterior

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

import import import import import import

/ / Tipo . / / D i s p o s i t i v o de s a l i d a / / ( pantalla ) . import j a v a . awt . G r a p h i c s C o n f i g u r a t i o n ; / / C o n f i g u r a c i n / / del d i s p o s i t i v o . import j a v a . awt . GraphicsEnvironment ; / / V a l o r e s d e l d i s p o s i t i v o . import import import import import import import import import j a v a . awt . event . WindowAdapter ; / / Escucha en l a ventana . j a v a . awt . event . WindowEvent ; / / Evento en l a ventana . j a v a . awt . event . A c t i o n L i s t e n e r ; j a v a . awt . event . A c t i o n E v e n t ; j a v a . awt . event . KeyEvent ; / / Evento de t e c l a . java . java . java . java . io io io io . File ; . FileReader ; . FileWriter ; . IOException ; // // // // Archivo . F l u j o de e n t r a d a . F l u j o de s a l i d a . Excepcin de e n t r a d a / s a l i d a .

j a v a . awt . C o n t a i n e r ; j a v a . awt . Dimension ; j a v a . awt . Rectangle ; j a v a . awt . I n s e t s ; j a v a . awt . Font ; j a v a . awt . GraphicsDevice ;

/ / Contenedor . / / Dimensin . / / Rectngulo .

import j a v a . u t i l . Locale ; / / Para que n u e s t r o programa hable en espa o l .

Qu variables de clase vamos a necesitar? En primer lugar, nuestro componente de texto deber ser una variable de clase para que podamos modicarlo de manera sencilla en todos nuestros mtodos. La etiqueta para decirle al usuario si se ha modicado el texto tambin nos conviene hacerla una variable de clase. Y siempre es conveniente hacer nuestra ventana principal una variable de clase. Usaremos una cadena para saber el nombre del archivo sobre el que estamos trabajando y un booleano para saber si el texto ha sido modicado. Vamos adems a aadir otra cadena para guardar el nombre del archivo (vamos a necesitarlo despus) y denamos constantes para el ancho y alto de nuestra ventana:
47 public class E d i t o r { 48 49 p r i v a t e JFrame marco ; 50 p r i v a t e JTextArea t e x t o ;

/ / Ventana p r i n c i p a l . / / El t e x t o del archivo . Contina en la siguiente pgina

162
Contina de la pgina anterior 51 52 53 54 55 56 57 58 59 60 61 p r i v a t e JLabel private String private String p r i v a t e boolean estado ; / / E l estado d e l a r c h i v o . archivoOriginal ; / / E l nombre d e l a r c h i v o o r i g i n a l . a r c h i v o ; / / E l nombre d e l a r c h i v o a c t u a l . modificado ; / / Tamao de bloque / / de b y t e s . = 640; / / Ancho de l a ventana . = 480; / / A l t u r a de l a ventana .

public s t a t i c f i n a l i n t BLOQUE = 512; public s t a t i c f i n a l i n t ANCHO public s t a t i c f i n a l i n t ALTO

La variable esttica BLOQUE [58], la vamos a usar para leer y escribir del disco duro.

Constructores
Tendremos dos constructores; uno sin parmetros y otro con una cadena como parmetro, que ser el nombre de un archivo a abrir. As podremos iniciar un editor vaco, sin ningn texto, y otro que abra un archivo y muestre su contenido.
65 66 67 public E d i t o r ( ) { this ( null ) ; }

75 76 77 78 79 80 81 82 83

public E d i t o r ( S t r i n g a r c h i v o ) { modificado = false ; archivoOriginal = this . archivo = archivo ; creaVentanaPrincipal ( ) ; i f ( archivo != null ) { abrirArchivoDeDisco ( ) ; } marco . s e t V i s i b l e ( t r u e ) ; }

Los dos constructores funcionan de manera idntica; slo que uno adems de inicializar todo, manda abrir un archivo de texto. Para evitar cdigo duplicado, hacemos que el constructor que no recibe parmetros mande llamar al constructor que recibe una cadena [66] y le pase null para distinguir cundo abrimos el archivo. El constructor inicializa en false la variable que nos dice si el archivo est modicado [76], inicializa las variables con el nombre del archivo [77] (que puede ser null),

Interfaces grcas

163

y manda crear la ventana principal [78]. Para esto suponemos que tendremos una funcin abrirArchivoDeDisco que abrir el archivo especicado por nuestra variable de clase archivo y mostrar su contenido en nuestro componente de texto. Siempre es bueno imaginar que tenemos funciones que hacen ciertas cosas, porque as vamos dividiendo el trabajo en tareas cada vez ms pequeas. Si queremos probar que el programa compila, slo hay que implementar las funciones vacas. Si el nombre de archivo que nos pasaron no es null [79], mandamos abrir el archivo en disco [80].Suponemos otro mtodo llamado abrirArchivoDeDisco que (como se puede sospechar) abrir el archivo del disco. Por ltimo, hacemos visible la ventana principal de nuestro programa [82].

Creacin de la ventana principal


Veamos ahora cmo crearemos la ventana principal con el mtodo creaVentanaPrincipal:
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 marco = new JFrame ( "Editor -- <ArchivoNuevo ) ; >" t e x t o = creaAreaDeTexto ( ) ; JMenuBar b a r r a = creaBarraDeMenu ( ) ; estado = new JLabel ( " " ) ; JPanel panel = new JPanel ( ) ; J S c r o l l P a n e s c r o l l P a n e = new J S c r o l l P a n e ( t e x t o ) ; s c r o l l P a n e . s e t P r e f e r r e d S i z e (new Dimension (ANCHO, ALTO ) ) ; panel . s e t L a y o u t (new BoxLayout ( panel , BoxLayout . Y_AXIS ) ) ; panel . add ( s c r o l l P a n e ) ; panel . add ( estado ) ; C o n t a i n e r c = marco . getContentPane ( ) ; c . add ( panel ) ; marco . setJMenuBar ( b a r r a ) ; marco . pack ( ) ; t e x t o . setRequestFocusEnabled ( t r u e ) ; t e x t o . requestFocus ( ) ; Contina en la siguiente pgina

164
Contina de la pgina anterior 114 115 116 117 118 119 120 121 122 123 marco . addWindowListener (new WindowAdapter ( ) { public void windowClosing ( WindowEvent e ) { menuQuitarPrograma ( ) ; } }); Rectangle medidas = o b t e n M e d i d a s P a n t a l l a ( ) ; marco . s e t L o c a t i o n ( ( medidas . width ANCHO) / 2 , ( medidas . h e i g h t ALTO ) / 2 ) ;

Primero, construimos nuestra ventana principal y le ponemos un ttulo apropiado [90]. Despus creamos el rea de texto [92], la barra del men [93] y la etiqueta con el estado del archivo [94]. Aqu volvemos a suponer dos mtodos, creaAreaDeTexto y creaBarraDeMenu. Creamos un panel [96] y una ventana corrediza [97]. A sta le pasamos nuestra rea de texto para que est dentro de ella, y luego le denimos el tamao con nuestras variables de clase estticas [98]. Despus le ponemos un administrador de trazado de caja [100], el cual slo acomoda uno tras otro a los componentes, ya sea vertical u horizontalmente. En particular, al nuestro le decimos que los acomode verticalmente (por eso el constructor recibe un BoxLayout.Y_AXIS). Aadimos nuestra ventana corrediza y nuestra etiqueta en el panel [101,102]. Sacamos la ventana contenedora de nuestra ventana principal [104] y le metemos el panel [105]. Tambin le ponemos la barra de men [107] y empacamos a la ventana [108]. Con esto se ajusta el tamao de los componentes. Queremos que cuando el programa empiece, el usuario pueda escribir inmediatamente, as que le pasamos el foco a nuestro componente de texto [110,111]. Por ltimo le aadimos un escucha a la ventana para que cuando el evento de cerrar ocurra (windowClosing), entonces el programa termine [114118]. Suponemos una nueva funcin: menuQuitarPrograma. El prejo menu es porque queremos que el evento de cerrar la ventana funcione igual que cuando seleccionemos la opcin de quitar el programa del men; para ambas usaremos este mtodo. Las dos ltimas lneas del mtodo [120,121] obtienen las dimensiones de la pantalla donde est corriendo el programa, para poder colocar la ventana de forma centrada. Esto es un adorno y es absolutamente prescindible, pero es el tipo de adornos que hacen agradables a los programas. Ah nos creamos otro mtodo: obtenMedidasPantalla. El mtodo es el que sigue:

Interfaces grcas

165

128 129 130 131 132 133 134 135 136 137 138 139 140 141

public Rectangle o b t e n M e d i d a s P a n t a l l a ( ) { Rectangle v i r t u a l B o u n d s = new Rectangle ( ) ; GraphicsEnvironment ge ; ge = GraphicsEnvironment . g e t L o c a l G r a p h i c s E n v i r o n m e n t ( ) ; GraphicsDevice [ ] gs = ge . getScreenDevices ( ) ; f o r ( i n t j = 0 ; j < gs . l e n g t h ; j ++) { GraphicsDevice gd = gs [ j ] ; G r a p h i c s C o n f i g u r a t i o n [ ] gc = gd . g e t C o n f i g u r a t i o n s ( ) ; f o r ( i n t i = 0 ; i < gc . l e n g t h ; i ++) { v i r t u a l B o u n d s = v i r t u a l B o u n d s . union ( gc [ i ] . getBounds ( ) ) ; } } return virtualBounds ; }

Est copiado tal cual de la clase GraphicsConguration, pero ah va una explicacin rpida: se crea un rectngulo [129], obtenemos el ambiente grco [131], del cual sacamos los dispositivos grcos disponibles [132], a cada uno de los cuales les sacamos la conguracin [135], y de todas las conguraciones hacemos una unin [137], para al nal regresar el rectngulo [140]. Y as obtenemos las medidas de la pantalla.

Creacin del rea de texto


Crear el rea de texto es relativamente sencillo:
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 public JTextArea creaAreaDeTexto ( ) { JTextArea t e x t o = new JTextArea ( ) ; t e x t o . s e t E d i t a b l e ( true ) ; t e x t o . s e t M a r g i n (new I n s e t s ( 5 , 5 , 5 , 5 ) ) ; t e x t o . s e t F o n t (new Font ( "Monospaced" , Font . PLAIN , 1 4 ) ) ; t e x t o . getDocument ( ) . addDocumentListener ( new DocumentListener ( ) { public void changedUpdate ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } public void i n s e r t U p d a t e ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } Contina en la siguiente pgina

166
Contina de la pgina anterior 163 164 165 166 167 168 169 public void removeUpdate ( DocumentEvent e ) { modificado = true ; estado . s e t T e x t ( "Modificado" ) ; } }); return t e x t o ; }

Creamos el componente de texto [149], lo hacemos editable [150], le ponemos margen de 5 pixeles alrededor [151], le ponemos una fuente monoespaciada [152] (eso signica que todos los caracteres tienen el mismo ancho), y le ponemos un escucha para que en cada uno de sus eventos, se ponga el estado del editor como modicado [153167] (cambiamos la variable modicado a true, y ponemos la cadena "Modificado" en la etiqueta estado). Esto tiene sentido, ya que los tres eventos implican modicar el texto de alguna manera. Por ltimo regresamos el componente [168].

Creacin de la barra de men


Crear la barra de men tampoco es difcil:
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 public JMenuBar creaBarraDeMenu ( ) { JMenuBar b a r r a = new JMenuBar ( ) ; JMenu menu ; JMenuItem e l ; menu = new JMenu ( "Archivo" ) ; menu . setMnemonic ( KeyEvent . VK_A ) ; b a r r a . add ( menu ) ; e l = new JMenuItem ( "Nuevo archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_N, A c t i o n E v e n t . CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuNuevoArchivo ( ) ; } }); Contina en la siguiente pgina

Interfaces grcas

167
Contina de la pgina anterior

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

menu . add ( e l ) ; e l = new JMenuItem ( "Abrir archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_O, A c t i o n E v e n t . CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuAbrirArchivo ( ) ; } }); menu . add ( e l ) ; e l = new JMenuItem ( "Salvar archivo" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_S , A c t i o n E v e n t . CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuSalvarArchivo ( ) ; } }); menu . add ( e l ) ; e l = new JMenuItem ( "Salvar archivo como..." ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuSalvarArchivoComo ( ) ; } }); menu . add ( e l ) ; menu . addSeparator ( ) ; e l = new JMenuItem ( "Quitar programa" ) ; e l . s e t A c c e l e r a t o r ( KeyStroke . getKeyStroke ( KeyEvent . VK_Q, A c t i o n E v e n t . CTRL_MASK ) ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuQuitarPrograma ( ) ; } }); menu . add ( e l ) ; menu = new JMenu ( "Ayuda" ) ; menu . setMnemonic ( KeyEvent . VK_Y ) ; b a r r a . add ( menu ) ; Contina en la siguiente pgina

168
Contina de la pgina anterior 232 233 234 235 236 237 238 239 240 241 242 e l = new JMenuItem ( "Acerca de..." ) ; e l . a d d A c t i o n L i s t e n e r (new A c t i o n L i s t e n e r ( ) { public void a c t i o n P e r f o r m e d ( A c t i o n E v e n t e ) { menuAcercaDe ( ) ; } }); menu . add ( e l ) ; return barra ; }

El mtodo es muy largo; pero hace casi lo mismo seis veces. Primero creamos la barra de men [175] y despus declaramos un men [176] y un elemento de men [177]. Vamos a usar dos mens (Archivo y Ayuda) y seis elementos de men (Nuevo archivo, Abrir archivo, Guardar archivo, Guardar archivo como. . . , Quitar programa y Acerca de. . . ); as que para no declarar ocho variables, declaramos slo dos y las usamos varias veces.2 Primero creamos el men Archivo [179] y le declaramos una tecla mnemnica [180]. La tecla que elegimos es la tecla a (por eso el KeyEvent.VK_A), y signica que cuando hagamos M-a (o Alt-a en notacin no-XEmacs), el men se activar. Y aadimos el men a la barra [181]. Despus creamos el elemento de men Nuevo archivo [183] y le ponemos un acelerador [184,185]. El acelerador es parecido a la tecla mnemnica; cuando lo presionemos, se activar la opcin de men. El acelerador que escogimos es C-n (la tecla es KeyEvent.VK_N y el modicador es ActionEvent.CTRL_MASK, o sea Control ). De una vez le ponemos un escucha al elemento del men [186190]. El escucha slo manda llamar al mtodo (que supusimos) menuNuevoArchivo. Por ltimo, aadimos el elemento de men al men [191]. Las lneas [192226] son una repeticin de esto ltimo; creamos los dems elementos de men, les ponemos aceleradores (no a todos) y les ponemos escuchas que solamente mandan llamar a algn mtodo, ninguno de los cuales hemos hecho. La nica lnea diferente es la [217], en la que aadimos un separador al men (un separador es una lnea nada ms). Y por ltimo, en las lneas [229238] creamos el men Ayuda de forma casi idntica al men Archivo. Lo ltimo que hace el mtodo es regresar la barra de men [241].
Usar una misma variable para distintos objetos es considerado una mala prctica de programacin; pero no es un pecado capital, y puede ser utilizado en casos semi triviales, como ste.
2

Interfaces grcas

169

Los eventos del men


Los eventos del men son los ms importantes, porque sern los que determinen cmo se comporta el programa. Cuando el usuario selecciona un elemento de men (MenuItem), o presiona un acelerador asociado a l, se dispara un evento de accin (action event), que se maneja con un escucha de accin o action listener. La interfaz ActionListener slo tiene un mtodo, actionPerformed, as que este escucha no tiene adaptador. Para dividir el trabajo, supusimos que tendramos un mtodo para cada una de las opciones del men. Los mtodos correspondientes a ste son: menuNuevoArchivo, menuAbrirArchivo, menuSalvarArchivo, menuQuitarPrograma, menuSalvarArchivoComo y menuAcercaDe, que va a ser lo siguiente que veamos. menuNuevoArchivo
248 249 250 251 252 253 254 255 256 257 public void menuNuevoArchivo ( ) { i f ( modificado ) { confirmarDejarArchivo ( ) ; } t e x t o . s e t T e x t ( "" ) ; modificado = false ; estado . s e t T e x t ( " " ) ; archivoOriginal = archivo = null ; marco . s e t T i t l e ( "Editor -- <ArchivoNuevo ) ; >" }

Lo primero que hace el mtodo es vericar que el archivo en la ventana no est modicado [249]. Si est modicado, entonces hay que conrmar si se deja o no el archivo [250]; para eso suponemos un mtodo conrmarDejarArchivo. Despus limpiamos el texto del componente de texto [252], y dejamos el estado del editor como no modicado [253,254]. Ponemos el nombre del archivo en null [255] (porque es nuevo) y cambiamos el ttulo de la pantalla al nuevo estado. Noten que la variable archivoOriginal la dejamos igual que a archivo. menuAbrirArchivo
264 265 266 267 public void menuAbrirArchivo ( ) { i f ( modificado ) { confirmarDejarArchivo ( ) ; } Contina en la siguiente pgina

170
Contina de la pgina anterior 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 J Fi l e C ho o s e r f c = n u l l ; try { S t r i n g d i r = System . g e t P r o p e r t y ( "user.dir" ) ; f c = new J F i l e C h o o s e r ( d i r ) ; } catch ( S e c u r i t y E x c e p t i o n se ) { f c = new J F i l e C h o o s e r ( ) ; } f c . s e t D i a l o g T i t l e ( "Abrir archivo" ) ; i n t r = f c . showOpenDialog ( marco ) ; i f ( r == J F i l e C h o o s e r . APPROVE_OPTION) { File f = fc . getSelectedFile ( ) ; i f ( ! f . exists ( ) ) { JOptionPane . showMessageDialog ( marco ,

"El archivo \""+ f + "\" no existe." , "El archivo no existe" ,


JOptionPane .ERROR_MESSAGE ) ; return ; } archivo = f . toString ( ) ; abrirArchivoDeDisco ( ) ; } }

Igual que menuAbrirArchivo, lo primero que hace el mtodo es comprobar si el archivo ha sido modicado [265] y si es as pide conrmacin respecto a si se quiere dejar el archivo [266]. Despus declaramos un JFileChooser [269]. Un JFileChooser abre una ventana de dilogo donde podemos seleccionar archivos. En un bloque try [270] tratamos de obtener el directorio de trabajo actual (current working directory) [271,272] para crear el JFileChooser. Si no podemos (o sea, si se lanza una excepcin) [273], usamos el directorio $HOME del usuario [274] (es lo que hace por default el constructor de JFileChooser). Le ponemos ttulo al JFileChooser [277], y obtenemos la opcin que haya presionado el usuario del dilogo [278]. Si el usuario presion la opcin Aceptar [279], obtenemos el archivo del dilogo [280] y comprobamos que exista [281]. Si el archivo no existe, mostramos un mensaje dicindolo [282287] y salimos de la funcin [288]. Si no entramos al cuerpo del if (el segundo if , anidado dentro del primero), entonces el archivo existe, as que obtenemos el nombre [291] y lo abrimos con abrirArchivoDeDisco

Interfaces grcas

171 [292], que an no tenemos. Si el usuario no presion Aceptar, entonces presion Cancelar, y ya no hacemos nada.
Actividad 10.8 Consulta la documentacin de la clase JOptionPane, y presta atencin al mtodo esttico showMessageDialog(). Haz lo mismo con la clase JFileChooser.

menuSalvarArchivo
302 303 304 305 306 307 308 309 310 311 public void menuSalvarArchivo ( ) { i f ( ! modificado ) { return ; } i f ( a r c h i v o == n u l l ) { menuSalvarArchivoComo ( ) ; } else { salvarArchivoEnDisco ( ) ; } }

Con este mtodo ocurre lo contrario a los primeros dos; si el archivo no ha sido modicado [303] entonces se sale del mtodo [304] (para qu lo salvamos si no ha habido cambios?). Despus comprueba si el nombre del archivo es null [306]. Si lo es signica que el archivo es nuevo, y entonces hay que hacer lo que el mtodo menuSalvarArchivoComo y, por lo tanto, lo manda llamar [307]. Si no, manda salvar el archivo en disco [309], con el mtodo salvarArchivoEnDisco. menuSalvarArchivoComo
319 320 321 322 323 324 325 326 public void menuSalvarArchivoComo ( ) { JFileChooser f c = null ; try { S t r i n g d i r = System . g e t P r o p e r t y ( "user.dir" ) ; f c = new J F i l e C h o o s e r ( d i r ) ; } catch ( S e c u r i t y E x c e p t i o n se ) { f c = new J F i l e C h o o s e r ( ) ; } Contina en la siguiente pgina

172
Contina de la pgina anterior 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 f c . s e t D i a l o g T i t l e ( "Salvar archivo como..." ) ; i n t r = f c . showSaveDialog ( marco ) ; File f = fc . getSelectedFile ( ) ; i f ( r == J F i l e C h o o s e r . APPROVE_OPTION) { i f ( f . exists ( ) ) { i n t r2 ; r 2 = JOptionPane . showConfirmDialog ( marco , "El archivo \""+ f + "\" existe.\n"+ "Desea sobreescribirlo?" , "El archivo ya existe" , JOptionPane . YES_NO_OPTION ) ; i f ( r 2 ! = JOptionPane . YES_OPTION) return ; } archivo = f . toString ( ) ; salvarArchivoEnDisco ( ) ; } }

En las lneas [320326] hacemos lo mismo que en el mtodo menuAbrirArchivo: crear un JFileChooser, si es posible con el directorio de trabajo actual. Le ponemos un ttulo adecuado [328], obtenemos la opcin que presion el usuario en el dilogo [329], y obtenemos el archivo que se seleccion [330]. Si el usuario eligi la opcin de Aceptar en el dilogo [331], se comprueba que el archivo no exista [332]. Si existe, le preguntamos al usuario si quiere sobreescribirlo [334340]. Si la respuesta es distinta de S, entonces salimos del mtodo [341,342]. Si no, obtenemos el nombre del archivo [344], y mandamos salvar el archivo [345]. Si el usuario no eligi la opcin de Aceptar, ya no hacemos nada. menuQuitarPrograma
354 355 356 357 358 359 public void menuQuitarPrograma ( ) { i f ( modificado ) { ; confirmarDejarArchivo ( ) ; } System . e x i t ( 0 ) ; }

Interfaces grcas

173

Si el archivo est modicado [355], pedimos conrmacin para dejarlo [356] y despus salimos del programa [358]. acercaDe
365 366 367 368 369 370 public void menuAcercaDe ( ) { JOptionPane . showMessageDialog ( marco , "Editor de texto sencillo" , "Acerca de..." , JOptionPane . INFORMATION_MESSAGE ) ; }

ste es el mtodo ms idiota de todos los tiempos; slo muestra un dilogo con informacin del programa (que por cierto no tiene ninguna informacin til).

El resto de los mtodos


Ya hemos casi terminado los mtodos que habamos supuesto tener. Slo nos faltan tres, que son realmente los que realizan el trabajo pesado: leer y escribir el archivo en disco. abrirArchivoDeDisco
380 381 382 383 384 385 386 387 388 389 390 391 392 public void a b r i r A r c h i v o D e D i s c o ( ) { char [ ] c a r a c t e r e s = new char [BLOQUE ] ; int leidos ; S t r i n g B u f f e r sb = new S t r i n g B u f f e r (BLOQUE ) ; try { F i l e R e a d e r f r = new F i l e R e a d e r ( a r c h i v o ) ; do { l e i d o s = f r . read ( c a r a c t e r e s ) ; sb . append ( c a r a c t e r e s , 0 , l e i d o s ) ; } while ( l e i d o s ! = 1 && l e i d o s == BLOQUE ) ; f r . close ( ) ; t e x t o . s e t T e x t ( sb . t o S t r i n g ( ) ) ; texto . setCaretPosition ( 0 ) ; Contina en la siguiente pgina

174
Contina de la pgina anterior 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 } catch ( IOException i o e ) { JOptionPane . showMessageDialog ( marco , "El archivo \""+ a r c h i v o + "\" no se pudo leer." , "Error al leer" , JOptionPane .ERROR_MESSAGE ) ; archivo = archivoOriginal ; return ; } archivoOriginal = archivo ; modificado = false ; estado . s e t T e x t ( "" ) ; marco . s e t T i t l e ( "Editor -- "+ a r c h i v o ) ; }

El mtodo primero crea un arreglo de carcteres [381], del tamao que denimos en la variable BLOQUES, y declara un entero para saber cuntos carcteres leemos en cada pasada [382]. Tambin declara una cadena variable (StringBuffer) [383]. Las cadenas variables son como las cadenas, con la ventaja de que pueden aumentar y disminuir de tamao, editarse los carcteres que tienen dentro, etc. Despus, dentro de un bloque try [384] abrimos el archivo de texto para lectura [385], y en un ciclo [386-389] leemos todos los carcteres del archivo y los metemos en la cadena variable. Cerramos el archivo [390], ponemos todos esos carcteres como el texto de nuestro componente de texto [391] y hacemos que lo que se vea del texto sean los primeros carcteres [392]. Si algo sale mal en el try [393], suponemos que no pudimos leer el archivo, se lo informamos al usuario [394399], regresamos el nombre del archivo al original [400] (si no hacemos esto podemos perderlo) y nos salimos de la funcin [401]. Si salimos airosos del try, actualizamos la variable archivoOriginal [403] y ponemos el estado del editor en no modicado [404406], lo que tiene sentido pues acabamos de abrir el archivo.
Actividad 10.9 Consulta la documentacin de la clase FileReader y fjate en los constructores y en los mtodos read y close.

Interfaces grcas

175

salvarArchivoEnDisco
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 public void s a l v a r A r c h i v o E n D i s c o ( ) { try { F i l e W r i t e r fw = new F i l e W r i t e r ( a r c h i v o ) ; fw . w r i t e ( t e x t o . g e t T e x t ( ) ) ; fw . c l o s e ( ) ; } catch ( IOException i o e ) { JOptionPane . showMessageDialog ( marco , "El archivo \""+ a r c h i v o + "\" no se pudo escribir." , "Error al escribir" , JOptionPane .ERROR_MESSAGE ) ; archivo = archivoOriginal ; return ; } archivoOriginal = archivo ; modificado = false ; estado . s e t T e x t ( "" ) ; marco . s e t T i t l e ( "Editor -- "+ a r c h i v o ) ; }

Dentro de un try [417], abrimos el archivo para escritura [418], le escribimos todo el texto de nuestro componente de texto [419] y cerramos el archivo [420]. Si algo sale mal [421], le decimos al usuario que no pudimos salvar su archivo [422427], regresamos el nombre del archivo al original [428] y salimos del programa [429]. Si salimos bien del try, actualizamos la variable archivoOriginal [431] y ponemos el estado del editor en no modicado [432434], lo que tiene sentido pues acabamos de salvar el archivo.
Actividad 10.10 Consulta la documentacin de la clase FileWriter y fjate en los constructores y en los mtodos write y close.

conrmarDejarArchivo
442 443 public void c o n f i r m a r D e j a r A r c h i v o ( ) { int r ; Contina en la siguiente pgina

176
Contina de la pgina anterior 444 445 446 447 448 449 450 451 452 r = JOptionPane . showConfirmDialog ( marco ,

"El archivo no se ha salvado.\n"+ "Desea salvarlo?" , "El archivo ha sido modificado" ,


JOptionPane . YES_NO_OPTION ) ; i f ( r == JOptionPane . YES_OPTION) menuSalvarArchivo ( ) ; }

Le avisamos al usuario que el archivo est modicado y preguntamos si quiere salvarlo [444449]. Si quiere, llamamos al mtodo menuSalvarArchivo [451], porque es equivalente.

El mtodo main
Por ltimo, hay que ver el mtodo main:
459 460 461 462 463 464 465 466 467 468 469 470 471 public s t a t i c void main ( S t r i n g [ ] args ) { i f ( args . l e n g t h > 1 ) { System . e r r . p r i n t l n ( "Uso: Editor [archivo]" ) ; System . e x i t ( 1 ) ; } Locale . s e t D e f a u l t (new Locale ( "es" , "MX" ) ) ; E d i t o r ed = n u l l ; i f ( args . l e n g t h == 0 ) { ; ed = new E d i t o r ( ) ; } else { ed = new E d i t o r ( args [ 0 ] ) ; } }

Lo que hace main es comprobar que a lo ms se llam al programa con un parmetro [460463]. Despus, dene el local del programa para que hable espaol de Mxico [464], declara un editor [465], y llama al constructor apropiado [466470].

Actividad 10.11 Consulta la documentacin de la clase Locale.

Interfaces grcas Actividad 10.12 Compila el archivo Editor.java y ejectalo. Est incluido en el archivo ejemplos.tar.gz.

177

Observaciones nales
Hay que reexionar un poco acerca del diseo de nuestro editor. Si se dieron cuenta, dentro de los manejadores de eventos (todos en clases internas annimas) se intent utilizar el menor cdigo posible. Poner mucho cdigo en los manejadores de eventos slo nos complica la vida y hace el cdigo ms feo. En general, se encapsula lo que hay que hacer en un manejador de eventos en un solo mtodo y slo se invoca. El diseo se hizo de forma descendiente (top-down); comenzamos haciendo los mtodos ms generales para despus hacer los ms particulares. Java se presta mucho para trabajar de esta forma, y en el caso de interfaces grcas nos facilita la vida ya que es muy sencillo pensar en una ventana principal y qu va a ocurrir cuando elijamos un men o presionemos un botn. Fjense cmo todos los mtodos son compactos. Cada mtodo hace una nica cosa y se asegura de hacerla bien, manejando las excepciones de acuerdo. Ningn mtodo es realmente largo; casi todos caben en una pantalla de texto normal, y los que no es porque tienen mucho cdigo de interfaces grcas (como la creacin de la barra de men). Ningn mtodo por s mismo debe ser difcil de entender. Noten tambin que casi no hay repeticin de cdigo excepto para cosas triviales. Es necesario explicar por qu todo el programa consiste de una clase. En el caso de este problema (hacer un editor de texto), todas las clases necesarias las provee Java: toda la parte de interfaces grcas (Swing) y toda la parte de entrada/salida (las clases del paquete java.io). Por lo tanto, slo tenamos que hacer la clase de uso, que es nuestro programa. Un ltimo aspecto respecto a la clase Locale. Es importante que se acostumbren a tratar de usar el idioma nativo del usuario para un programa. Todas las cadenas que aparecen en clases como JFileChooser o JMessageDialog estn por omisin en ingls. Java por suerte proporciona la habilidad de cambiar dinmicamente esas cadenas, de acuerdo a la lengua que queramos usar. Java maneja el concepto de locales, que es la manera en que determina cmo presentar cierta informacin al usuario. Esto no slo se aplica a en qu idioma imprimir "S" o "Archivo", sino a muchas diferencias que hay entre idiomas, e incluso entre idiomas iguales en distintos pases. Por ejemplo, en Mxico escribimos 1,000.00 para representar un millar con dos cifras decimales; pero en Espaa escriben 1.000,00 para representar lo mismo. Y en ambos pases se habla espaol (o eso dicen en Espaa). Los locales de Java manejan todo este tipo de asuntos.

178 La lnea
464 Locale . s e t D e f a u l t (new Locale ( "es" , "MX" ) ) ;

dene como local por omisin al que habla espaol ("es"), en Mxico ("MX"). Pueden comentar esa lnea y volver a compilar y ejecutar el programa para que vean la diferencia. Fjense en particular cuando usamos las clases JFileChooser y JOptionPane. Los cdigos de lenguaje y pas usados para determinar los locales estn denidos en el estndar ISO-639 e ISO-3166 respectivamente. El primero lo pueden consultar en
<http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt>

y el segundo en
<http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html>

Ejercicios
1. Haz una interfaz grca para la Base de Datos. La ventana principal de la aplicacin mostrar tres campos vacos, que representarn el nombre, la direccin y el telfono; usa tres etiquetas para sealizar cul es cual. El usuario debe poder introducir cadenas en estos campos. Adems, la ventana tendr tres botones; uno que dir Agregar, otro que dir Borrar y otro que dir Buscar. En caso de que el usuario haga click en el botn Agregar, se deber comprobar que el usuario haya introducido datos en los campos y que sean correctos (o sea, que el telfono sea un entero vlido); de ser as, crear un RegistroAgenda y agregarlo a la base de datos. Despus de hacerlo, deber volver a dejar vacos los campos. Si el registro se repite, debe utilizar un dilogo para avisarle al usuario que no pudo agregar el registro porque ya exista uno igual. Si alguno de los campos est vaco cuando el usuario haga click en Agregar, un dilogo deber informarle que no puede agregar un registro incompleto. Si el usuario hace click en Buscar, se deber comprobar que slo el campo del nombre o el del telfono tenga informacin. De ser as, se realizar una bsqueda por nombre o por telfono (dependiendo de cul de los dos tenga informacin). Debe comprobarse que si es el campo del telfono el que tiene informacin, que sta sea un entero vlido. Si se encuentra el registro, los campos deben actualizarse con los datos del registro. Si no se encuentra, un dilogo deber decirle al usuario que el nombre (o telfono) que busc no existen.

Interfaces grcas

179 Si se hace click en Buscar sin que haya informacin en el campo del nombre o el del telfono, un dilogo deber decirle al usuario que necesita uno de ambos para buscar un registro. Al momento en que el usuario haga click en Borrar, debe haber informacin en los tres campos. De no ser as, un dilogo debe informar al usuario que no se puede borrar un registro del que no se tiene toda la informacin. Aqu se le deja al programador una de varias opciones. La primera es que el usuario pueda borrar cualquier registro si de memoria teclea los datos que le corresponden y despus hace click en Borrar. En este caso el programador tiene que hacer primero una bsqueda, despus una comprobacin con el mtodo equals, y en caso de que exista el registro, borrarlo y si no existe avisarle al usuario que est tratando de borrar un registro inexistente. La segunda es que el usuario slo pueda borrar un registro que haya buscado antes. Esta estrategia tiene la ventaja de que se garantiza que el registro exista y se evita la necesidad de buscarlo. Pero hay que tener siempre una referencia al ltimo registro buscado. Tambin es posible implementar ambas; aunque es particularmente engorroso. Se deja a criterio del programador cul debe de implementar. Adems de todo esto, el programa debe tener una barra de men con dos entradas: Base de Datos y Ayuda. En la entrada Base de Datos deben existir (al menos) las opciones Nueva Base de Datos, Guardar Base de Datos, Recuperar Base de Datos y Salir del programa. Cada una de las opciones hace lo que su nombre indica. Pon lo que quieras en la entrada de Ayuda, pero no la dejes vaca. Varios aspectos que el programa debe cumplir: Nunca debe abortar. El programa debe ser indestructible. No importa lo que pase, el programa debe poder continuar corriendo a menos que el usuario cierre la ventana o seleccione la opcin Salir del programa del men Base de Datos. O que caiga un meteoro en la computadora. Esto quiere decir, en primer lugar, que todas las excepciones deben manejarse de manera inteligente. Nunca debe terminar el programa, aunque no pueda leer o escribir la base de datos. Tampoco debe de terminar por alguna excepcin del tipo de NullPointerException. Asegrate de que todos tus mtodos funcionen con el caso de que la lista de registros sea null. Si algo sale mal, siempre debe ser avisado el usuario, con un mensaje claro y compacto. Se probarn todos los mtodos que se han visto en el curso. Se probar el poder buscar y agregar registros, as como el poder guardar y el poder recuperar la base de datos. Se probarn todos estos mtodos tambin cuando

180 la lista de registros sea null. Debe funcionar el programa en todos estos casos. El programa no debe permitir el perder informacin sin avisar al usuario. Si algn registro ha sido aadido o borrado de la base de datos, y el usuario quiere salir del programa, debe preguntrsele primero si quiere guardar la base de datos en disco duro. Para esto, el programa debe estar siempre consciente de si la base de datos ha sufrido alguna modicacin. 2. (Opcional) Hay muchas restricciones sobre cundo puede o no hacer click el usuario sobre los botones Agregar, Borrar y Bsqueda. Utiliza los mtodos setEnabled e isEnabled de la clase JButton (heredado el primero de AbstractButton y el segundo de Component), para que el usuario realmente slo pueda hacer click en uno u otro botn cuando las condiciones lo permitan.

Preguntas
1. Hay alguna parte del editor que no comprendas? 2. Qu se te antoja hacer con interfaces grcas? Explcate.

Prctica: Ant y archivos Jar

11

Any program will expand to ll available memory. Murphys Laws of Computer Programming #11

Meta
Que el alumno profundice sus conocimientos de Ant y que aprenda a utilizar archivos Jar.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: comprender mejor el funcionamiento de Ant y el compilador de Java y utilizar archivos Jar.

182

Desarrollo
Hemos usado Ant a lo largo de esta prcticas sin hacer mucho enfsis en cmo funciona y qu es exactamente lo que hace. Tambin hemos usado archivos Jar durante casi todo el material, sin decir cmo se crean o qu son. En esta prctica nos enfocaremos a ver exactamente cmo funcionan estas dos herramientas de Java.

Ant
No es posible entender del todo a Ant si no explicamos primero las herramientas de Java, de las cuales las ms importantes son el compilador y JavaDoc, aunque tambin se incluyen muchas otras en el Java Developers Kit (JDK) de Java.

El compilador de Java
Hasta esta prctica, hemos compilado nuestros programas haciendo:
# ant compile

u otro objetivo de nuestro archivo build.xml. Qu es lo que ocurre realmente cuando hacemos esto? El objetivo compile generalmente ha tenido esta forma:
< t a r g e t name="compile"> <mkdir d i r ="build" / > < j a v a c s r c d i r ="src" d e s t d i r ="build" / > </ target>

Desde la prctica 2 vimos que <mkdir... /> y <javac... /> son tareas (tasks) de Ant. La tarea mkdir es obvia; crea el directorio build, a menos que ya exista. La tarea javac se ha hecho evidente a lo largo de las prcticas: compila todos los archivos .java que hay debajo del directorio src/ y coloca los correspondientes archivos .class en el directorio build/, si compilan claro. Pero, cmo hace esto? Como dijimos en la prctica 1, Ant realmente no hace mucho; recarga casi todo el trabajo en el compilador de Java, javac. El compilador de Java puede ser ms que suciente si estamos compilando una sola clase: digamos que la clase Matriz2x2 y la interfaz MatrizCuadrada estuvieran en un mismo directorio y que no pertenecieran a ningn paquete. Entonces podramos compilarlas con javac directamente sin ningn problema, haciendo lo siguiente en el directorio donde estuvieran los archivos Matriz2x2.java y MatrizCuadrada.java:

Ant y archivos Jar # javac Matriz2x2.java

183

El compilador de Java es lo sucientemente listo como para descubrir por s mismo que para poder compilar la clase Matriz2x2, necesitar a la interfaz MatrizCuadrada, porque Matriz2x2 la implementa. Los archivos .class quedaran en el mismo directorio donde estn los .java. Para programas pequesimos de una o dos clases, javac es ms que suciente. El problema es que generalmente Java no se utiliza para programas pequesimos de una o dos clases. Con la clase Matriz2x2 ocurre adems que est en el paquete icc1.practica4; entonces no podramos compilarla como lo hicimos arriba. El porqu de esto es algo complicado y lo veremos en un momento. Para compilar a Matriz2X2 podramos hacer lo siguiente: en el directorio padre del directorio icc1 (que tiene que existir porque la clase Matriz2x2 est en el paquete icc1.practica4), hacemos:
# javac icc1/practica4/Matriz2x2.java \ icc1/practica4/MatrizCuadrada.java

Los archivos .class tambin terminaran en el mismo directorio de los archivos .java. Aqu es donde las cosas comienzan a ponerse divertidas. Si tenemos muchos archivos1 , la situacin puede alcanzar niveles ridculos fcilmente. Por supuesto, si tuviramos muchas clases en un solo paquete, las cosas podran simplicarse:
# javac icc1/practica4/*.java

Pero esto slo nos sirve si tenemos slo un paquete2 . Para compilar todas nuestras prcticas, habra que hacer:
# javac icc1/practica1/*.java icc1/practica2/*.java \ ... icc1/practica10/*.java

Y eso slo si todas las prcticas comparten el mismo directorio icc1. Aqu ya comenz a ponerse incmodo el asunto, pero se pone peor. Si realmente estuvisemos compilando la prctica 4, entonces necesitaramos a la clase Consola y eso signica que necesitamos de la ruta de clases (classpath).
Y con Java parece que siempre terminan existiendo muchos archivos. Y el problema es que, adems de muchas clases, tambin siempre parece terminar habiendo muchos paquetes.
2 1

184

La ruta de clases (classpath)


La ruta de clases, o classpath en ingls, ayuda al compilador de Java a encontrar las clases que necesita para poder compilar las clases que se le piden, o a la mquina virtual a encontrar clases que se necesiten para correr un programa. Si llamamos al compilador o a la mquina virtual sin especicarle ninguna ruta de clases, por omisin slo incluir a las clases de la biblioteca estndar de Java (las clases que Java provee siempre). Pero si para compilar o correr un programa necesitamos usar clases distintas de las de la biblioteca estndar de Java, entonces necesitaremos especicar la ruta de clases. En qu consiste la ruta de clases? Bsicamente es una lista de directorios, que apuntan a los paquetes de las clases que queremos. Por ejemplo, la clase Consola es del paquete icc1.interfaz y eso quiere decir que vive en un directorio interfaz, que a su vez es subdirectorio de icc1. Supongamos que el directorio icc1 est en un directorio llamado /usr/lib/java; entonces para compilar la prctica 4 necesitaramos hacer:
# javac -classpath /usr/lib/java icc1/practica4/*.java

Los archivos compilados tambin terminaran en el directorio icc1/practica4. Esto no es conveniente por muchas razones, una de ellas es que al momento de distribuir el programa tal vez querremos dar al mundo el cdigo fuente, o tal vez los binarios; pero rara vez querremos dar ambos al mismo tiempo. Para esto el compilador de Java provee una opcin para que las clases se compilen en un directorio distinto al del cdigo fuente. Con este objeto podramos hacer un directorio llamado build y llamar al compilador con la opcin -d:
# mkdir build # javac -d build -classpath /usr/lib/java icc1/practica4/*.java

Por supuesto, en estas prcticas slo han necesitado las clases de los paquetes icc1.*, pero desde proyectos medianamente complejos se terminan utilizando muchas ms clases que estn en distintas bibliotecas y seguramente en varios directorios. Y tambin tenemos la ventaja de que slo queremos compilar clases del paquete icc1.practica4. Pero, qu pasara si quisiramos tambin compilar clases de otros paquetes que no necesariamente tuviesen todos como paquete raz a icc1? Ocurrira que nuestra lnea de compilacin sera algo como lo siguiente (la lista de la ruta de clases utiliza dos puntos : para separar sus distintos elementos)
# javac -d build \ -classpath /usr/lib/bibl1:/usr/lib/bibl2:... \ icc1/practica4/*.java \ pak1/subpak1/*.java ...

Ant y archivos Jar

185

Y rpidamente comienza a salirse de control el asunto. Con Java no es raro tener un proyecto que consiste en cientos de clases, repartidos en decenas de paquetes y utilizando mltiples bibliotecas. Aqu es donde Ant nos facilita la vida.

La ayuda de Ant
La lnea de compilacin de arriba:
# javac -d build \ -classpath /usr/lib/bibl1:/usr/lib/bibl2:... \ icc1/practica4/*.java \ pak1/subpak1/*.java ...

queda reducida con Ant a un objetivo as (moviendo todos los paquetes con nuestro cdigo fuente a un directorio src):
< t a r g e t name="compile"> <mkdir d i r ="build" / > < j a v a c s r c d i r ="src" d e s t d i r ="build"> <classpath> <pathelement="/usr/lib/bibl1" / > <pathelement="/usr/lib/bibl2" / > ... < / classpath> < / javac> </ target>

Y van a decir, qu ventaja tiene eso? Incluso se termina escribiendo ms. La cosa es que slo hay que escribirlo una nica vez, y si hay que modicarlo, slo hay que hacerlo en un sitio (el archivo build.xml). Pero adems, podemos meter cuantos paquetes querramos en el directorio src y stos sern automticamente compilados, no importa cuntas clases tenga cada uno. Por supuesto, conforme crece el tamao de nuestros proyectos, la ayuda de Ant se hace ms y ms obvia. Supongamos que en nuestro proyecto tenemos distintos mdulos, que no queremos compilar juntos siempre, pero que necesitan la misma ruta de clases para compilarse. Entonces podemos hacer esto:
10 11 12 13 <path i d ="compile.classpath"> <pathelement="/usr/lib/bibl1" / > <pathelement="/usr/lib/bibl2" / > < / path > Contina en la siguiente pgina

186
Contina de la pgina anterior 14 15 < t a r g e t name="compile.module1"> 16 <mkdir d i r ="module1/build" / > 17 < j a v a c s r c d i r ="module1/src" d e s t d i r ="module1/build"> 18 < c l a s s p a t h r e f i d ="compile.classpath" / > 19 < / javac> 20 </ target> 21 22 < t a r g e t name="compile.module2"> 23 <mkdir d i r ="module2/build" / > 24 < j a v a c s r c d i r ="module2/src" d e s t d i r ="module2/build"> 25 < c l a s s p a t h r e f i d ="compile.classpath" / > 26 < / javac> 27 </ target>

Cada mdulo tendra su propio directorio (modulo1 y modulo2). Y si alguna vez hay que modicar la ruta de clases, slo hay que hacerlo en un lugar. Pero adems Ant nos permite tener dependencias entre objetivos. Supongamos que el mdulo 1 siempre tuviera que compilarse antes que el mdulo 2; entonces podramos modicar a este ltimo objetivo as:
22 23 24 25 26 27 < t a r g e t name="compile.module2" depends="compile.module1"> <mkdir d i r ="module2/build" / > < j a v a c s r c d i r ="module2/src" d e s t d i r ="module2/build"> < c l a s s p a t h r e f i d ="compile.classpath" / > < / javac> </ target>

y con ello, si tratamos de compilar el mdulo 2 sin haber compilado el mdulo 1, Ant automticamente compilar el mdulo 1 por nosotros. Ant tiene tareas para manejar no slo el compilador, sino tambin JavaDoc, el depurador de Java y muchas ms, entre ellas la misma mquina virtual, lo que nos permite poder correr nuestros programas a travs de Ant. Adems, ofrece una biblioteca (en Java, por supuesto), para que uno pueda crear tareas que hagan casi cualquier cosa. A lo largo de estas prcticas se han dado archivos build.xml que permiten manejar pequeos proyectos (como lo han sido las prticas). Ant es una herramienta increblemente poderosa y no podemos cubrir todas sus funciones en una prctica. Sin embargo, con lo que se les ha dado es ms que suciente para que puedan comenzar a utilizar Ant y estudiar por su cuenta las tareas que ofrece y cmo hacer ustedes sus propias tareas de Ant.

Ant y archivos Jar Actividad 11.1 Consulta la documentacin de Ant en su pgina en <http://ant.apache.org>.

187

Archivos Jar
Supongamos ahora que ya tenemos un programa enorme con cientos de clases, dividido en varios paquetes, que adems utiliza otras varias decenas de clases distribuidas en otras varias decenas de paquetes. Nuestro programa ya compila y corre y, orgullosos, queremos decirle al mundo de l y presumirlo con pompa y circunstancia. As que ponemos una pgina en la WWW y pedimos a la gente que baje y pruebe nuestro programa. No podemos decirles que bajen todos nuestros archivos .class y que despus los acomoden en la jerarqua de directorios necesaria (de hecho s podramos decirles eso, otra cosa es que lo hicieran). Tenemos que encontrar la manera de distribuir nuestras clases y paquetes de una manera sencilla y eciente. De la misma forma, muchas veces querremos usar bibliotecas y al compilar no necesariamente tener que pasarle a la ruta de clases un directorio. Nos gustara poder pasarle slo un archivo por biblioteca, por ejemplo. Para todo esto estn los archivos Jar. Los archivos Jar (jarles en ingls) se llaman as por Java Archives o archivos de Java. Aqu se entiende archivo como chero en el sentido bibliogrco, no como archivo en disco duro.

Uso y creacin de archivos Jar


Ya hemos usado archivos Jar. Durante todo el curso, varias clases (de hecho varios paquetes) se les distribuyeron en un solo archivo Jar. Un archivo Jar es slo un archivo con formato de compresin ZIP, que incluye todas las clases de uno o varios paquetes, organizadas en los directorios correspondientes. Adems, un archivo Jar puede contener imgenes o sonidos necesarios para que un programa se ejecute como debe ser. Como el archivo Jar contiene dentro de s una estructura de directorios, podemos pasrselo directamente a la ruta de clases del compilador o de Ant para utilizarlo, como hicimos a lo largo del curso utilizando la tarea <classpath/>. El ejemplo que vimos arriba podra quedar as:
10 11 12 13 <path i d ="compile.classpath"> <pathelement="lib/bibl1.jar" / > <pathelement="lib/bibl2.jar" / > < / path >

188

si tuvisemos los archivos Jar bibl1.jar y bibl2.jar en un directorio lib. As no parece que haya mucha ganancia; pero podramos mejorarlo as:
10 11 12 13 14 <path i d ="compile.classpath"> < f i l e s e t d i r ="lib"> < i n c l u d e name="**/*.jar" / > </ fileset> < / path >

Con eso, cualquier archivo Jar que pongamos en el directorio lib quedar automticamente incluido en la ruta de clases. Lo cual es muy conveniente si para compilar un proyecto necesitamos varios archivos Jar. Supongamos que queremos crear un archivo Jar para la prtica 10. Slo nos ponemos en el directorio build (despus de compilar, claro) y hacemos
# jar cf practica10.jar icc1/practica10/*.class

El comando jar es el utilizado para generar los archivos Jar.


Actividad 11.2 Consulta la pgina del manual de jar haciendo # man jar

En pocas palabras, la c es para decirle que debe crear el archivo Jar y la f es para decirle cmo se llamar el archivo. Java tiene un paquete especializado para tratar con archivos Jar programticamente (esto es, dentro de programas escritos en Java). Es el paquete java.jar.
Actividad 11.3 Consulta la documentacin de las clases en el paquete java.jar.

Tambin podemos crear archivos Jar desde nuestro build.xml. Slo utilizamos la tarea <jar/>; por ejemplo, igual para nuestra prctica 10:
50 51 52 < t a r g e t name="practica10.jar" depends="compile"> < j a r j a r f i l e ="practica10.jar" b a s e d i r ="build" / > </ target>

Con esto creamos el archivo Jar utilizando como base nuestro directorio build. Hacemos que el objetivo dependa de la compilacin, para slo crear el archivo Jar si la prctica ya fue compilada exitosamente.

Ant y archivos Jar

189

Ejecutar archivos Jar


Lo que hicimos arriba archiva todas las clases de nuestra prctica en el archivo Jar
practica10.jar. Cmo podemos correr un programa teniendo slo el archivo Jar con las

clases? Una manera es, sencillamente, utilizando el archivo Jar en la ruta de clases:
# java -classpath icc1.jar:practica10.jar \ icc1.practica10.UsoBaseDeDatosAgenda

(Necesitamos pasarle tambin el archivo Jar icc1.jar a la ruta de clases para que el programa corra). Pero esto es feo. Recordemos que el programa de las torres de Hanoi les fue proporcionado en un archivo Jar, y que lo nico que tuvieron que hacer para correrlo fue
# java -jar hanoi.jar 5

La opcin -jar le dice a la mquina virtual que hay que ejecutar la clase principal del archivo Jar. As que, cmo le especicamos la clase principal a un archivo Jar? La respuesta es sencilla; el archivo Jar debe incluir un archivo llamado MANIFEST en su raz. Por ejemplo en nuestro ejemplo de la prctica 10, el archivo debera estr en el directorio build antes de crear el archivo Jar. En el archivo maniesto (el archivo MANIFEST) pueden ir varias cosas, pero en particular podemos crearlo con el siguiente contenido: Main-Class: icc1.practica10.UsoBaseDeDatosAgenda Si un archivo MANIFEST con ese contenido est en el directorio build al momento de crear el archivo Jar, podremos ejecutar nuestra prctica 10 de la siguiente manera:
# java -jar practica10.jar

Actividad 11.4 Investiga qu ms puede incluirse en el archivo maniesto.

Ejercicios
1. Como vimos, para hacer un archivo Jar ejecutable directamente por la mquina virtual necesitamos incluirle un archivo maniesto. Vimos que lo ms sencillo es crear el archivo en el directorio build.

190 Sin embargo, nuestro objetivo clean siempre borra ese directorio, as que perderamos nuestro archivo maniesto siempre que limpiramos. En tu build.xml crea un objetivo llamado jar, que al ser invocado de la siguiente manera
ant jar

genere el archivo practica11.jar, y que cumpla lo siguiente a) Que el archivo practica11.jar ejecutable contenga el archivo maniesto correcto. b) Que el objetivo clean an exista y an borre el directorio build cada vez que se llame. c) Que el objetivo jar siga funcionando llamemos o no al objetivo clean. Con este ejercicio terminamos con nuestra base de datos en el curso. Por supuesto, no es una base de datos en todo el sentido del trmino (seguramente complementarn su concimiento sobre las bases de datos), pero varias de los aspectos que hemos visto se aplican. Slo queremos especicar algo respecto a las bases de datos reales que ignoramos a lo largo de todas estas prcticas, porque s es un concepto importante. Durante todo el curso manejamos la idea de que una base de datos es una tabla. En la prctica 5 cambiamos la representacin interna con una lista, pero conceptualmente podamos seguirla viendo como una tabla (cada registro como un rengln, cada campo como una columna). Las bases de datos reales son un conjunto de tablas, organizadas alrededor de relaciones entre las columnas de las tablas.

Preguntas
1. Al momento de compilar, cul es la diferencia entre especicar un archivo Jar o un directorio con una jerarqua de paquetes en la ruta de clases?

Prctica: Hilos de ejecucin y enchufes

12

The value of a program is proportional to the weight of its output. Murphys Laws of Computer Programming #12

Meta
Que el alumno aprenda a utilizar hilos de ejecucin y a programar en red con enchufes.

Objetivos
Al nalizar esta prctica el alumno ser capaz de: entender y utilizar hilos de ejecucin y entender y utilizar enchufes.

192

Desarrollo
A lo largo de las prcticas se ha cubierto el material para Java de un primer curso de programacin. Queremos sin embargo cubrir dos puntos que, aunque probablemente algo avanzados para un primer curso de programacin, creemos es necesario mencionarlos y discutirlos, ya que en la actualidad son conocimientos obligatorios para cualquier profesional de la computacin El propsito de esta ltima prctica es mencionar los hilos de ejecucin y la programacin en red con enchufes.

Hilos de ejecucin
La ejecucin de nuestros programas, hasta la prctica 10, ha sido lineal. Se invoca al mtodo main de nuestra clase de uso, y a partir de ah se invoca a los mtodos A, B, C, etc., de distintas clases, en orden. Dentro de cada uno de esos mtodos se puede a la vez llamar a ms mtodos (y en la prctica 8 vimos lo que ocurra cuando un mtodo se llamaba a s mismo). Y desde la prctica 5 sabemos que dentro de un mtodo puede haber uno o varios ciclos dando vueltas. Incluso podemos tener ciclos dentro de ciclos. Pero al n y al cabo, si nuestras funciones recursivas estn bien hechas y nuestros ciclos terminan algn da, el programa continuar su ejecucin tranquilamente, una instruccin a la vez. Las aplicaciones modernas dejaron de funcionar as hace ya mucho tiempo. Uno tiene editores como XEmacs donde se puede compilar un programa en un buffer mientras se edita un archivo en otro buffer al mismo tiempo. O navegadores como Firefox que pueden abrir varias pginas de la red al mismo tiempo; y algunas de esas pginas pueden reproducir msica o video; y adems el navegador puede estar bajando varios archivos a la red, todo al mismo tiempo. Por supuesto, para que todo esto funcione, el sistema operativo debe proveer de la funcionalidad necesaria. Para ello, hace ya casi cuarenta aos surgi el concepto de multiproceso, que es lo que permite que hagamos varias cosas en la computadora al mismo tiempo. A pesar de que en la actualidad ya no es tan raro encontrar computadoras personales con dos o incluso cuatro procesadores, la gran mayora de las computadoras de escritorio siguen contando con un nico procesador. Un procesador slo puede ejecutar una instruccin a la vez. Los procesadores actuales pueden ejecutar varios millones de instrucciones en un segundo, pero una a una. Incluso en las computadoras con mltiples procesadores, cada procesador slo puede ejecutar una instruccin a la vez.

*Hilos de ejecucin y enchufes

193

Los sistemas operativos reparten el procesador entre varios procesos (aunque algunos lo hacen mucho peor que otros). Como los procesadores son muy rpidos (y cada vez lo son ms), no se nota que, en cada instante dado, el procesador slo se hace cargo de un proceso. Gracias a la capacidad multiproceso de los sistemas operativos, los lenguajes de programacin implementan un comando para que el proceso de un programa pueda dividirse en dos procesos (proceso padre y proceso hijo), cada uno de los cuales puede seguir caminos muy distintos. La instruccin suele llamarse fork. El problema de dividir procesos es que cuando se hace, toda la imagen del proceso padre en memoria se copia para el proceso hijo. Adems, son procesos completamente independientes; si el proceso padre abre archivos por ejemplo, no puede compartirlos con el proceso hijo. Imagnense un programa como Firefox o el Internet Explorer. Son programas grandes y ocupan mucho espacio en memoria. Cuando el usuario solicita que el navegador comience a bajar un archivo de la red, queremos que pueda seguir navegando mientras el archivo se descarga. Si dividimos el proceso, toda la imagen en memoria de Firefox debe copiarse, cuando lo nico que queremos es la parte del programa que se encarga de bajar archivos. Y toda esa memoria tiene que liberarse una vez que el archivo se haya descargado. Adems, imaginen que al usuario se le ocurre bajar siete archivos al mismo tiempo (a algn usuario se le va a ocurrir que eso es una buena idea. . . ). En vista de lo costoso que resulta dividir procesos, surgi el concepto de procesos ligeros o hilos de ejecucin (threads en ingls).1 Un hilo de ejecucin es como un proceso hijo; pero se ejecuta en el contexto de su proceso padre. Pueden compartir variables, intercambiar informacin, y lo que muchos consideran lo ms importante: no se copia toda la imagen en memoria del proceso padre. Aunque es posible que no se hayan dado cuenta, nosotros ya hemos usado varios hilos de ejecucin. Al momento de empezar a utilizar eventos, comenzamos a utilizar varios hilos de ejecucin en un programa. Cuando tenemos un programa orientado a eventos (como lo son la gran mayora de los programas que utilizan una interfaz grca para comunicarse con el usuario), la parte que espera que los eventos ocurran se ejecuta en un hilo de ejecucin distinto al del programa principal. Fjense en el mtodo main de la clase Editor (o el de su propia interfaz grca para su base de datos). Despus de crear el objeto de la clase Editor con new, el programa principal termina. Ya no se ejecuta ningn mtodo o instruccin; main sencillamente acaba. Ese hilo de ejecucin termina (porque aunque usemos un nico hilo de ejecucin
Los hilos de ejecucin ya existan en Algol extendido, lenguaje de programacin que usaban las mquinas Burroughs (hoy Unisys) en 1970. Todos los sistemas Unix, que estn basados en el estndar POSIX, utilizan los Posix Threads implementados en el leguaje de programacin C, desde hace ya varios aos. El concepto no es nuevo, pero comenz a popularizarse hasta hace relativamente pocos aos.
1

194 en un programa, ste sigue siendo un hilo de ejecucin). La ejecucin del programa contina porque hicimos visible un objeto de la clase JFrame con el mtodo setVisible. En ese momento comienza a ejecutarse otro hilo de ejecucin que pueden imaginarse como el siguiente cdigo:
Event e ; do { e = eventoActual ( ) ; i f ( e != null ) { / * E j e c u t a todos l o s manejadores d e l evento * / } } while ( t r u e ) ;

Por supuesto, no es as exactamente, pero sa es la esencia. El hilo de ejecucin de los componentes grcos de Java es un ciclo innito, que lo nico que hace es detectar qu eventos ocurren y ejecutar los manejadores correspondientes. Si lo piensan tiene sentido; en un programa con interfaz grca (XEmacs, Firefox, nuestro editor, etc.), si ustedes no hacen nada el programa tampoco. Se queda esperando hasta que hagan algo para reaccionar de forma correspondiente. Se queda esperando en un ciclo que nunca termina. Adems, todos los manejadores de eventos se ejecutan en otro hilo de ejecucin. As que cuando trabajamos con interfaces grcas en Java, siempre se estn usando al menos tres hilos de ejecucin.

Hilos de ejecucin en Java


Por sorprendente que resulte, en Java los hilos de ejecucin son clases.

Actividad 12.1 Consulta la documentacin de la clase Thread y de la interfaz Runnable.

Un hilo de ejecucin es un objeto de la clase Thread, o de alguna clase que la extienda. Antes de que usramos hilos de ejecucin, si un mtodo A tena este cuerpo:
{ / / I n s t r u c c i n 1 B ( ) ; / / Llamamos a l mtodo B . / / I n s t r u c c i n 2

lo que ocurra al entrar al mtodo era:

*Hilos de ejecucin y enchufes

195

1. Se ejecutaba la instruccin 1. 2. Se llamaba al mtodo B (ejecutndose todas las instrucciones, ciclos, recursiones, etc. del mtodo). 3. Se ejecutaba la instruccin 2. Con un hilo de ejecucin, en cambio, tenemos esto (si suponemos que tenemos un hilo de ejecucin llamado t):
{ / / I n s t r u c c i n 1 t . start (); / / I n s t r u c c i n 2

/ / Llamamos a l mtodo s t a r t / / d e l h i l o de e j e c u c i n .

En este caso, el mtodo start se ejecuta al mismo tiempo que la instruccin 2. El mtodo start regresa inmediatamente despus de haber sido llamado y ahora la ejecucin del programa corre en dos hilos de ejecucin paralelos. En los dos hilos de ejecucin podemos hacer cosas y los hilos de ejecucin pueden verse y hablarse (pasarse objetos y tipos bsicos). Despus de todo lo que se dijo al inicio de la seccin, ustedes saben que los dos hilos de ejecucin no se ejecutan al mismo tiempo exactamente. Pero la JVM, con la ayuda del sistema operativo, se encarga de que parezca que s se ejecutan al mismo tiempo.

Creacin de hilos de ejecucin


Para crear un hilo de ejecucin, necesitamos extender la clase Thread:
public class MiProceso extends Thread { ...

Dentro de la clase MiProceso lo que hacemos es sobrecargar el mtodo run, que no recibe ningn parmetro y tiene tipo de regreso void:
public void run ( ) { / / Aqu implementamos n u e s t r o h i l o de e j e c u c i n . }

Despus, cuando queramos ejecutar el hilo de ejecucin, llamamos al mtodo start que se hereda de la clase Thread, y start a su vez ejecuta el mtodo run.

196
public class UsoMiProceso public s t a t i c void main MiProceso mp = . . . / / ... // mp. s t a r t ( ) ; // ... // { ( S t r i n g [ ] args ) { Construimos e l h i l o de e j e c u c i n . Hacemos ms cosas . Ejecutamos e l h i l o de e j e c u c i n . Hacemos todava ms cosas .

No llamamos directamente a run justamente porque start se encarga de hacer lo necesario para que run se ejecute en su propio hilo de ejecucin. El mtodo start regresa inmediatamente, y por lo tanto la ejecucin del programa ahora sigue dos caminos (dos hilos de ejecucin): las expresiones que siguen despus de llamar a start y las expresiones dentro del mtodo run de nuestro objeto de la clase MiProceso. Dentro de cualquiera de los dos hilos de ejecucin se pueden hacer llamadas a distintos mtodos, recursin, ciclos, etc. Todo esto se vera grcamente como en la gura 12.1
Figura 12.1 Ejecucin de un hilo de ejecucin

Mtodo main

(otras expresiones)

Mtodo start

Mtodo run

(otras expresiones)

(otras expresiones)

Noten que no es obligatorio que el mtodo start sea llamado desde main; puede ser llamado desde cualquier lugar en el programa (por eso ponemos que hay otras posibles expresiones desde que entramos a main hasta que llamamos a start). Extender a la clase Thread siempre que queramos un hilo de ejecucin independiente puede resultar restrictivo. Tal vez en el diseo de nuestro problema nos encontremos

*Hilos de ejecucin y enchufes

197

con una clase que est dentro de una jerarqua de herencia especca, y que adems tiene que ejecutarse en su propio hilo de ejecucin. Para esto est la interfaz Runnable. Cuando una clase que hagamos implemente la interfaz Runnable, tiene que denir el mtodo run (que es el nico mtodo declarado en la interfaz):
public class MiProceso extends AlgunaClase implements Runnable { public void run ( ) { ... } ...

Y para ejecutar nuestro hilo de ejecucin creamos un objeto de la clase Thread, utilizando el objeto de nuestra clase. Con este nuevo objeto podemos ya llamar al mtodo start:
public class UsoMiProceso { public s t a t i c void main ( S t r i n g [ ] args ) { MiProceso mp = . . . / / Construimos e l o b j e t o r u n n a b l e . ... / / Hacemos ms cosas . Thread t ; / / Declaramos un h i l o de e j e c u c i n , y t = new Thread (mp ) ; / / l o c o n s t r u i m o s con n u e s t r o o b j e t o . t . start (); / / Ejecutamos e l h i l o de e j e c u c i n . ... / / Hacemos todava ms cosas .

En el ejemplo pusimos que la clase MiProceso extiende a la clase AlgunaClase. Esto es porque slo hay que implementar la interfaz Runnable si nuestra clase tiene que heredar a alguna otra que no sea Thread. Si nuestra clase no necesita extender a ninguna otra, lo correcto es que extienda a Thread.

Vida y muerte de los hilos de ejecucin


Hay que tener bien claro cmo empiezan, cmo terminan y cmo se ejecutan los hilos de ejecucin. Un hilo de ejecucin slo comienza cuando, con el mtodo start, se llama al mtodo run. Cuando creamos un nuevo hilo de ejecucin con new, el hilo de ejecucin se considera vaco: la JVM no ha hecho todava nada con l para que se pueda ejecutar en paralelo. Si llamamos a cualquier mtodo del hilo de ejecucin antes de que llamemos al mtodo start, la JVM lanzar la excepcin IllegalThreadStateException. Una vez que llamamos al mtodo start, la JVM hace todo lo que se necesita para que el hilo de ejecucin se ejecute. Esto implica comunicarse con el sistema operativo, asignar memoria, etc., y entonces manda llamar al mtodo run del hilo de ejecucin. El hilo de ejecucin termina cuando sale del mtodo run. Dentro de este mtodo puede hacer recursiones, mandar llamar otros mtodos, hacer ciclos y todo lo que se

198 hace en un mtodo normal; pero tarde o temprano (si el mtodo est bien hecho) termina, y entonces el hilo de ejecucin termina tambin, con lo que se liberan todos los recursos de la computadora que pudiera haber utilizado. Por ello generalmente tendremos un ciclo en el mtodo run (o llamaremos a un mtodo del hilo de ejecucin que tenga un ciclo), que continuar hasta que algo ocurra o deje de ocurrir. Mientras el hilo de ejecucin est corriendo, podemos hacer varias cosas con l. La ms importante de ellas es sincronizarlo con otros hilos de ejecucin, pero veremos esto un poco ms adelante. Otra cosa que podemos hacer es ponerlo a dormir durante un determinado nmero de milisegundos:
try { Thread . s l e e p ( 1 0 0 0 ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { / / No podemos d o r m i r ahora , as que seguimos . }

El mtodo esttico sleep de la clase Thread tratar de poner a dormir el hilo de ejecucin donde se manda llamar el mtodo (por eso es esttico). El mtodo espera de la clase GracosDeReloj de las primeras dos prcticas es lo que haca. Puede ocurrir que la JVM decida que el hilo de ejecucin no puede dormirse en ese momento, por lo que el mtodo lanza la excepcin InterruptedException cuando eso ocurre. Realmente no hay mucho que hacer en esos casos; sencillamente el hilo de ejecucin continuar su ejecucin sin dormirse. Otra cosa que podemos hacer es preguntar si un hilo de ejecucin est vivo o no:
i f ( mp. i s A l i v e ( ) ) { / / E l h i l o de e j e c u c i n e s t v i v o . }

Este mtodo se invoca desde afuera del hilo de ejecucin que nos interesa, no adentro. Si un hilo de ejecucin est vivo quiere decir que ya fue llamado su mtodo run y que ste no ha terminado. Adems de esto, podemos cambiar la prioridad de un hilo de ejecucin. La prioridad de un hilo de ejecucin es la importancia que le da la JVM para ejecutarlo. Como discutimos arriba, una computadora generalmente tiene un procesador, y en algunos casos dos o cuatro. La JVM (junto con el sistema operativo) tiene que repartir el procesador entre todos los hilos de ejecucin que existan. En principio, trata de ser lo ms justa que puede, asignndole a cada hilo de ejecucin relativamente la misma cantidad de tiempo en el procesador. Podemos cambiar eso con el mtodo setPriority. Sin embargo, no es un mtodo conable en el sentido de que no va a funcionar igual entre arquitecturas distintas o entre sistemas operativos diferentes. Pueden realizar experimentos con el mtodo, pero real-

*Hilos de ejecucin y enchufes

199

mente se recomienda no basar el funcionamiento de un programa en las prioridades de sus hilos de ejecucin.

Sincronizacin de hilos de ejecucin


En los ejemplos presentados hasta ahora, hemos visto los hilos de ejecucin como tareas totalmente independientes ejecutndose al mismo tiempo. La mayor parte de las veces, sin embargo, querremos que los hilos de ejecucin compartan informacin o recursos. Esto trae problemas, por supuesto. Los hilos de ejecucin deben ponerse de acuerdo en cmo van a tener acceso a la informacin o recursos para que no se estorben mutuamente. Hay un ejemplo bastante viejo, que hace mencin a cinco lsofos sentados alrededor de una mesa circular. Enfrente de cada lsofo hay un tazn de arroz y a la derecha de cada lsofo hay un palillo. Para comer un bocado de arroz, cada lsofo necesita el palillo que le corresponde y adems el palillo a su izquierda (que le toca a otro lsofo). Si todos los lsofos agarran su palillo al mismo tiempo, ninguno va a poder comer. Es necesario que se pongan de acuerdo de alguna manera para que algunos puedan agarrar dos palillos mientras otros esperan, y despus ir cambiando turnos. El ejemplo es de sistemas operativos, ya que sirve para ejemplicar de manera muy sencilla lo que pasa cuando varios procesos quieren usar el procesador (o la memoria o el disco duro). El caso en que el sistema se traba porque todos los procesos quieren usar el mismo recurso al mismo tiempo (como cuando los lsofos se quedaban cada uno con nada ms un palillo) se le llama abrazo mortal o deadlock en ingls. Para evitar los abrazos mortales, lo primero que debemos hacer es evitar que dos hilos de ejecucin distintos traten de llamar al mismo mtodo al mismo tiempo. Para esto hacemos que el mtodo est sincronizado, lo que se logra agregando la palabra clave synchronized en la denicin del mtodo:
public synchronized void metodo ( ) { ...

Esto se hace en los mtodos de los objetos que vayan a ser compartidos por los hilos de ejecucin, no en los mtodos de un mismo hilo de ejecucin (se puede hacer tambin, pero realmente no tiene mucho sentido). Casi todas las clases de Java tienen sincronizados sus mtodos. Adems de sincronizar mtodos completos, podemos sincronizar un bloque respecto a una o varias variables (si suponemos que queremos sincronizar respecto al objeto obj):

200
synchronized ( o b j ) { o b j . metodo ( ) ; obj . defineVariable ( 5 ) ; }

Cuando el hilo de ejecucin llegue a esa parte del cdigo y mientras est en el bloque, ningn otro hilo de ejecucin podr hacer uso del objeto obj. La segunda cosa que debemos hacer para evitar abrazos mortales es hacer que un hilo de ejecucin espere hasta que le avisen que ya puede tener acceso a algn recurso u objeto. Por ejemplo, en nuestro problema de los lsofos podemos hacer que los lsofos 2 y 4 esperen a que los lsofos 1 y 3 hayan usado los palillos para que ellos los usen; y que el lsofo 5 espere a los lsofos 2 y 4; y por ltimo que los lsofos 1 y 3 esperen a que el 5 acabe para continuar.2 Para que un hilo de ejecucin espere hasta que le avisen que ya puede seguir su ejecucin, est el mtodo wait:
try { / / espearamos a que nos a v i s e n que podemos c o n t i n u a r . wait ( ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { }

El mtodo wait est denido en la clase Object, as que todos los objetos de Java pueden llamarlo. El mtodo lanza la excepcin InterruptedException si por algn motivo no puede esperar. Adems, en la clase Object estn denidas otras dos versiones del mtodo wait, que sirven para que el hilo de ejecucin slo espere un determinado nmero de milisegundos (o nanosegundos, si el sistema operativo puede manejarlos). El mtodo notifyAll (tambin denido en la clase Object) hace que todos los hilos de ejecucin que estn detenidos por haber llamado al mtodo wait continen su ejecucin. Con estos dos mtodos podemos hacer que los hilos de ejecucin se pongan de acuerdo en cundo deben tener acceso a algn recurso u objeto, para que no traten de hacerlo todos al mismo tiempo.

Grupos de hilos de ejecucin


Nada ms para no dejar de mencionarlo, cada hilo de ejecucin en Java pertenece a un grupo de hilos de ejecucin. Un hilo de ejecucin que no especique a qu grupo pertenece se asigna al grupo de hilos de ejecucin por omisin.
sta es una solucin que garantiza que todos los lsofos comen, pero es ineciente porque mientras el lsofo 5 come podra hacerlo tambin algn otro lsofo. Hay muchas soluciones distintas para el problema de los lsofos y los palillos.
2

*Hilos de ejecucin y enchufes

201

A un grupo de hilos de ejecucin se les puede tratar como conjunto, lo que permite que los pongamos a dormir o a esperar a todos al mismo tiempo. Todo lo dems que tenga que ver con grupos de hilos de ejecucin queda fuera del alcance de esta prctica.

Diseo con hilos de ejecucin


Hay que entender que los hilos de ejecucin son clases de uso de alguna manera. Son tareas que nuestro programa debe realizar al mismo tiempo que realiza su tarea principal (entre comillas porque todos los hilos de ejecucin, por lo menos por omisin, tienen la misma prioridad unos sobre otros). Las clases que extendamos de Thread o en que implementemos Runnable no se mapearn con algn elemento concreto de nuestro problema: se van a mapear con una tarea especca de nuestro programa, que no puede realizarse antes o despus de la tarea principal, sino que tiene que realizarse al mismo tiempo. Piensen que hacemos un programa que representa un restaurante. Nuestras clases obvias seran las que representarn al cocinero, a los meseros, al capitn y a los comensales. Pero adems tenemos que hacer que el cocinero haga los platillos, que los meseros soliciten rdenes, que el capitn reciba ms comensales, y que los clientes coman, todo al mismo tiempo. Para esto hacemos que cada una de esas tareas sea un hilo de ejecucin. Hay que tener en cuenta los abrazos mortales cuando se disea un programa con varios hilos de ejecucin. Los objetos que vayan a ser compartidos por varios hilos de ejecucin es recomendable que sus mtodos sean sincronizados; debe hacerse un anlisis profundo del problema para ver si es necesario utilizar los mtodos wait y notifyAll. Habr ocasiones en que podr evitarse. Hemos mencionado varias veces objetos compartidos por los hilos de ejecucin y que stos pueden hablarse y verse. El mtodo run no recibe parmetros, as que cmo le pasamos informacin a los hilos de ejecucin? Por suerte, los hilos de ejecucin son clases, y adems clases que nosotros mismos denimos. Por lo tanto podemos simplemente usar el constructor de nuestra clase que extienda a Thread para pasarle objetos o conjuntos de objetos (usando listas o arreglos).

Programacin en red
Desde hace varios aos, las aplicaciones que trabajan sobre la red han aumentado en nmero e importancia. Hoy en da casi cualquier profesionista necesita de un navegador y de un cliente de correo electrnico para trabajar. La programacin en red, tambin llamada de diseo cliente/servidor, es un tema en s mismo amplio y fuera del alcance de estas prcticas. Sin embargo, se les dar una

202 pequea muestra de cmo funciona para que vean los fundamentos bsicos de este tipo de programas. Para crear programas que funcionen sobre la red usando Java hay varios mtodos, de los cuales los ms conocidos son: Enchufes, que es la forma tradicional de escribir programas de diseo cliente/servidor y que tienen su propia biblioteca en Java. Los enchufes (sockets en ingls) envan y reciben bytes, o sea que funcionan a bajo nivel. RMI, o Remote Method Invocation, (Invocacin Remota de Mtodos). Es la versin en Java del RPC del lenguaje de programacin C (Remote Procedure Call o Llamado Remoto de Procedimientos). Consiste en transmitir objetos completos a travs de la red de una mquina a otra, donde pueden ejecutar sus mtodos. Servlets y JSP. JSP signica Java Servlet Page y funciona muy similarmente a PHP o las pginas ASP de Microsoft (PHP signica PHP: Hypertext Preprocessor mientras que ASP se entiende por ActiveX Server Page). Los servlets son programas de Java normales que se ejecutan en una mquina que funciona como servidor de WWW y que generan pginas dinmicamente. Los JSP, as como PHP y las ASP funcionan similarmente; pero en lugar de ser programas normales, es cdigo incrustado dentro de una pgina HTML. Todos estos mtodos se utilizan para crear programas cuyos clientes necesitan un navegador para ejecutarse, como Firefox o el Internet Explorer. CORBA. CORBA es un sistema de ejecucin de programas distribuido, que excede por mucho en complejidad, poder y teora a cualquiera de los anteriores. La meta ltima de CORBA es poder tener una mquina A ejecutando un programa, y que en un programa ejecutado desde otra mquina B se pueda utilizar un objeto creado en el programa de la mquina A. Las mquinas A y B pueden ser la misma o estar a kilmetros de distancia. Lo interesante de CORBA, es que no est atado a ningn lenguaje; la idea es que los programas de la mquina A y B pueden estar escritos en Java y C, o Perl y Python, o en C++ y Ada. CORBA permitira que los objetos de cada uno de estos lenguajes se comuniquen entre s. CORBA es un ejemplo de lo que en software se conoce como middleware, ya que funciona como intermediario entre aplicaciones construidas en distintos tipos de plataformas. En esta prctica veremos enchufes, ya que existen en casi todos los lenguajes de programacin del universo y porque son relativamente sencillos de utilizar, al precio de no proveer tanta funcionalidad o nivel de abstraccin como el RMI, los Servlets/JSP o CORBA.

*Hilos de ejecucin y enchufes Actividad 12.2 Consulta la documentacin de los paquetes java.net, java.rmi, y org.omg.CORBA.

203

Enchufes
Los enchufes, contrario a lo que pudiera pensarse, funcionan como enchufes. Piensen en el enchufe telefnico; es un punto de entrada/salida al exterior. Pueden recibir informacin a travs de l (cuando escuchan), y mandar informacin a travs de l (cuando hablan). De hecho, pueden hablar y or al mismo tiempo; pueden recibir y enviar informacin al mismo tiempo. La idea de los enchufes es crear puntos de entrada/salida entre dos computadoras; una vez creados, las computadoras podrn enviarse bytes mutuamente a travs de ellos. Es importante sealar que la comunicacin se reduce a bytes, por lo que son de muy bajo nivel. Para establecer la conexin entre dos enchufes, se necesitan dos programas. Se podra hacer la conexin con un solo programa utilizando hilos de ejecucin, pero no tiene sentido ya que lo que queremos es comunicar dos mquinas. El primer programa se llama servidor y lo que hace es estar escuchando en un puerto de comunicacin de la mquina donde est corriendo, esperando por una solicitud de conexin. Cuando la recibe crea un enchufe, y si la solicitud es vlida, la comunicacin queda establecida. El segundo programa se llama cliente, y lo que hace es crear un enchufe con la direccin de la mquina y el puerto donde est escuchando el servidor. Si el servidor est en esa mquina escuchando en ese puerto, la conexin queda establecida. Una vez que la conexin ha sido establecida, cada enchufe dispone de un objeto de la clase InputStream y de otro objeto de la clase OutputStream. Cuando un enchufe manda bytes a su OutputStream, el otro los recibe por su InputStream y viceversa. El control de los enchufes queda totalmente en manos del programador. De acuerdo a la aplicacin se ver cmo cada enchufe controla los mensajes que manda y recibe. El objeto de la clase InputStream de los enchufes tiene implementado el mtodo available, por lo que siempre podemos saber si hay algo que leer de l. Si el metodo available regresa un entero mayor que cero, entonces hay algo que leer. Si regresa cero no hay nada que leer.

El servidor
Lo que hace un servidor se resume en las siguientes lneas:

204

try { i n t p u e r t o = 10000; ServerSocket s e r v i d o r = new ServerSocket ( p u e r t o ) ; Socket c l i e n t e = s e r v i d o r . accept ( ) ; / / Lo ponemos a escuchar . m an ej a Cl i en t e ( c l i e n t e ) ; } catch ( E x c e p t i o n e ) { / * Crear un enchufe de s e r v i d o r y p o n e r l o a escuchar es p o t e n c i a l m e n t e p e l i g r o s o y puede r e s u l t a r en que sean lanzadas v a r i a s excepciones . * / }

El servidor se queda detenido en la llamada al mtodo accept y no sale de ah hasta que alguna solicitud se reciba. Cuando se recibe la solicitud, el servidor crea un enchufe que conecta con el enchufe del cliente y ah termina su funcin; a partir de ese momento el programa utiliza al enchufe que devuelve accept para comunicarse con el cliente. Dentro de manejaCliente se establece la manera en que el servidor maneja los mensajes enviados y recibidos. De acuerdo a la aplicacin, puede que el servidor se limite a mandar informacin, o tal vez slo la reciba. Lo ms comn, sin embargo, es que haga ambas cosas constantemente. Si se quiere hacer un servidor para mltiples clientes, se hace algo de este estilo:
try { i n t p u e r t o = 10000; ServerSocket s e r v i d o r = new ServerSocket ( p u e r t o ) ; while ( t r u e ) { / / Nos ponemos a escuchar . Socket c l i e n t e = s e r v i d o r . accept ( ) ; / / Creamos un h i l o de e j e c u c i n para que maneje a l enchufe . MiThread p r o c e s o C l i e n t e = new MiThread ( c l i e n t e ) ; / / Disparamos a l h i l o de e j e c u c i n . procesoCliente . s t a r t ( ) ; } } catch ( E x c e p t i o n e ) { }

De esta manera el servidor escucha eternamente por el puerto; cuando una conexin se recibe, dispara un hilo de ejecucin que maneja al enchufe, de la misma manera que lo hara manejaCliente, y vuelve a esperar por otra conexin. sta es la manera en que funcionan casi todos los servidores en la red (HTTP, FTP, TELNET, SSH, etc.) Si se dan cuenta, un servidor implementado as es un programa orientado a eventos, aunque no tenga interfaz grca. Los eventos en este caso son las solicitudes de conexin que recibe el servidor.

*Hilos de ejecucin y enchufes

205

Un mismo servidor puede tener un nmero potencialmente innito de enchufes conectados a un mismo puerto; sin embargo siempre se limita a un nmero jo las conexiones concurrentes posibles.3

Actividad 12.3 Consulta la documentacin de la clase ServerSocket, en el paquete java.net.

El cliente
El cliente es todava ms sencillo. Para crear el enchufe slo se necesita la direccin del servidor y el puerto donde est escuchando:
try { i n t p u e r t o = 10000; / / Puede u t i l i z a r s e un IP num r i c o , como " 1 3 2 . 2 4 8 . 2 8 . 6 0 " . S t r i n g d i r e c c i o n = "abulafia.fciencias.unam.mx" ; Socket s e r v i d o r = new Socket ( d i r e c c i o n , p u e r t o ) ; manejaServidor ( s e r v i d o r ) ; } catch ( E x c e p t i o n e ) { / * Crear un enchufe de c l i e n t e tambin genera v a r i a s p o s i b l e s excepciones . * / }

En la creacin del enchufe se realiza la conexin con el servidor. Es la funcin manejaServidor la que se encarga de manejar los mensajes enviados y recibidos.

Actividad 12.4 Consulta la documentacin de la clase Socket, del paquete java.net.

Analisis de un chat
Tienes a tu disposicin el cdigo fuente de dos clases: Servidor.java y Cliente.java. Son el servidor y el cliente de un chat. Un chat es un espacio virtual donde varios usuarios se conectan. Pueden mandar mensajes y los mensajes que envan son vistos por todos los usuarios conectados al chat, incluidos ellos mismos. Son bastante comunes en la red y han sido objeto de estudios
3

El ancho de banda no es gratuito.

206 sociolgicos y de comportamiento de masas (hay gente que asegura, en pblico incluso, haber conocido a sus parejas en un chat). Cmo funciona un chat de verdad? Hay un servidor, que es el cuarto del chat. Lo nico que hace el servidor es estar escuchando por conexiones. Cada vez que se realiza una conexin, el servidor crea un nuevo hilo de ejecucin para manejar al nuevo usuario, y avisa de esto a todos los usuarios conectados. Cada hilo de ejecucin del servidor est escuchando todo el tiempo a ver si su cliente dice algo. Si es as, manda de regreso el mensaje a todos los clientes, incluido el suyo. Esto es importante: cada hilo de ejecucin debe poder comunicarse con los dems. El cliente funciona muy similarmente; se conecta al servidor y se queda esperando mensajes. Si los recibe, lo nico que hace es imprimirlos. Para que el cliente mande mensajes, realmente se necesitara otro hilo de ejecucin; mas de eso se encargar la interfaz grca, que como ya sabemos tiene su propio hilo de ejecucin. La clase Servidor a la que tienes acceso es algo intil: slo permite una conexin a la vez. Sin embargo te permitir ver cmo funciona la conexin con enchufes.
Actividad 12.5 Compila las clases Servidor y Cliente. El build.xml incluido con los archivos genera los archivos Jar servidor.jar y cliente.jar. Para correr el servidor, una vez compilado, ejecuta # java -jar servidor.jar El servidor detectar la direccin de la mquina donde lo ests corriendo, y seleccionar por omisin el puerto 1234. Puedes especicar otro puerto con la opcin -p as: # java -jar servidor.jar -p 4321 Ahora ejecuta el cliente (en otra mquina de ser posible) con la siguiente lnea de comandos: # java -jar cliente.jar -s <direccionDelServidor>\ -p <puerto> Necesitars pasarle la direccin de la mquina donde est el servidor, con la opcin -s. Si el servidor utiliza el puerto por omisin (1234), puedes omitir la opcin -p.

Es tarea tuya comprender cmo funcionan ambas clases.

Ejercicios
1. Basndote (si quieres) en el servidor que slo recibe una conexin, utiliza hilos de ejecucin para que pueda recibir varias conexiones.

*Hilos de ejecucin y enchufes

207

Preguntas
1. Piensa en todos los programas que conoces, ya sea que funcionen en Unix/Linux o en cualquier otro sistema operativo. Crees poder hacerte una idea de cmo estn programados? Justica ampliamente.

Apndice A El resto de las leyes


Program complexity grows until it exceeds the capabilities of the programmer who must maintain it. Murphys Laws of Computer Programming #13

Undetectable errors are innite in variety, in contrast to detectable errors, which by denition are limited. Murphys Laws of Computer Programming #14

Adding manpower to a late software project makes it later. Murphys Laws of Computer Programming #15

Make it possible for programmers to write programs in English, and you will nd that programmers can not write in English. Murphys Laws of Computer Programming #16

The documented interfaces between standard software modules will have undocumented quirks. Murphys Laws of Computer Programming #17

210 The probability of a hardware failure disappearing is inversely proportional to the distance between the computer and the customer engineer. Murphys Laws of Computer Programming #18

ndice alfabtico
+, 34, 35, 62 , 34 *, 34, 36 /, 34, 36 %, 34 ++, 16, 34, 37 , 34, 37 >, 34, 37 >=, 35, 37 <, 34, 37 <=, 34, 37 <<=, 35, 37 >>>=, 35, 37 ?:, 35, 38 [], 34

. (punto), 53 . (punto), 14, 34 (<parmetros>), 34 (<tipo>), 34 -classpath, 125 abstract, 89, 90, 115, 117 acceso, 5760 acceso de paquete, 58 acceso privado, 15, 58 acceso protegido, 58, 9192 acceso pblico, 58 arreglos, 109114, 126 bloques, 11, 39 de ejecucin, 12, 22 break, 73, 7879 bytecode, 3, 5, 6, 8, 11 clases, 4261 clases abstractas, 89 clases annimas, 152157 clases nales, 91 clases fbrica, 60 clases internas, 152157 comentarios, 1718 comentarios para JavaDoc, 6667 componentes grcos, 144157 adaptadores, 150151 administradores de trazado, 146

==, 63 ==, 35, 37 !=, 35, 37 &&, 35, 38 ||, 35, 38 !, 34 &, 35 |, 35, 36 , 34 , 35 >>, 34 <<, 34 >>>, 34 =, 34, 35, 37 +=, 35, 37 =, 35, 37 *=, 35, 37 /=, 35, 37 %=, 35, 37 &=, 35, 37 |=, 35, 37 =, 35, 37 >>=, 35, 37

212 componentes atmicos, 145, 159 160 barra de progreso, 159 botones, 159 caja de combinaciones, 159 campo de texto, 145, 159 etiqueta, 145, 159 lista, 159 mens, 159 pista, 160 rangos, 159 selector de archivos, 160 selector de color, 160 soporte para texto, 160 tabla, 160 rbol, 160 componentes de primer nivel, 145, 157158 applet, 157 dilogo, 145, 157 marco, 157 componentes intermedios, 145, 158 159 barra de herramientas, 158 marco interno, 158 panel, 145, 158 ventana corrediza, 158 ventana de carpeta, 158 ventana dividida, 158 ventana en capas, 158 ventana raz, 159 empacamiento, 148 escuchas, 147150 escucha de ratn, 148 eventos, 146147 manejadores de evento, 147 constructores, 12, 5051, 91 continue, 7879 conversin explcita de tipos, 96, 97 conversin explcita de tipos, 31 do ... while, 7778

NDICE ALFABTICO encapsulamiento, 42 interfaces, 84 enchufes, 204208 cliente, 205, 207 servidor, 205207 excepciones, 130138 bloque catch, 130 bloque nally, 130 bloque try, 130 lanzar excepciones, 130 manejadores de excepcin, 132 expresiones, 3839 extends, 88, 89, 98 false, 31, 33, 35, 53, 74 ltros, 106107 ujos, 105106 de entrada, 106 de salida, 106 for, 7778 herencia, 8891 herencia mltiple, 97 interfaces, 9798 superclase, 92 hilos de ejecucin, 194203 abrazo mortal, 201 dividir procesos, 195 grupos de hilos de ejecucin, 202 hilo de ejecucin vaco, 199 multiproceso, 194 prioridad, 200 sincronizacin, 201 tareas, 203 if, 17, 72, 7476 implements, 98 import, 24, 55, 84, 95, 141 inicializacin, 32 instanceof, 35 jarles, 189 archivos Jar, 191

NDICE ALFABTICO java, 24, 25 javac, 2, 24 javadoc, 1819 jerarquas de componentes, 145, 157, 161 de herencia, 9295, 136, 199 de paquetes, 189 JVM, 2, 3, 11, 25, 33, 62, 64, 65, 97, 110, 114, 135, 197, 199, 200 listas, 7982, 94 anterior, 80 cabeza, 79 siguiente, 79 literales, 2932 main clases de uso, 5457 main, 11, 13, 22 clases de uso, 8 los argumentos de main, 113114 punto de entrada, 9, 11 mtodos, 9, 14, 16, 43, 4650 rma de un mtodo, 90 mdotos nales, 91 mtodos estticos, 6061 nombre, 47, 52 parmetros, 13, 14, 47 paso por valor, 53 tipo de regreso, 47 new, 12, 34 null, 33, 53, 72, 74 objetos, 42 construccin de objetos, 12 estado de un objeto, 16, 45, 57 operadores, 3438 corto circuito, 38 precedencia, 36 paquetes, 184 patrones, 99 pila de ejecucin, 130, 134 polimorsmo, 5153 portabilidad, 3 recolector de basura, 6465 conteo de referencias, 65 recursin, 122126 caer en loop, 123 clusula de escape, 122 denicin recursiva, 79 factorial, 122123 las Torres de Hanoi, 123125 recursin doble, 125 referencias, 15, 3233 apuntar, 33 return, 7879 reutilizacin de cdigo, 42 sobrecargar mtodos, 88, 90 super, 90, 91 switch, 7276

213

this, 48, 50 tipos, 2932 fuerte tipicacin, 29, 31, 32, 48 49 tipos bsicos, 30, 46 cadenas, 6164 clases envolventes, 9596 true, 31, 33, 35, 74 variables, 2932 alcance de una variable, 29, 45 declaracin de variables, 12 variables de clase, 15, 43, 4546 variables estticas, 60 variables nales, 60 variables locales, 15, 29, 49 vida de una variable, 45 while, 7678, 80

214 XEmacs buscar dentro de un buffer, 15 coloreacin de sintaxis, 23 compilar con XEmacs, 3

NDICE ALFABTICO

S-ar putea să vă placă și