Está en la página 1de 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 suficiente 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
LTEX, utilizando el paquete listings, de Carsten Heinz, a menos que el paquete fuera
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 >, significa que puede ser reemplazado por cadenas proporcionadas por el alumno. Por ejemplo
# java <NombreDeClase>

significa que <NombreDeClase> puede ser reemplazado por cualquier nombre de clase.
Tambin significar 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 modificar clases

3. Variables, tipos y operadores

21

4. Interfaces y clases por dentro

41

5. Estructuras de control y listas

71

6. Herencia

87

7. Entrada/salida y arreglos

103

8. Recursin

121

9. Manejo de excepciones

129

10. Interfaces grficas

143

11. Ant y archivos Jar

181

12. *Hilos de ejecucin y enchufes

191

A. El resto de las leyes

209

Prctica:
Ant y el
compilador de
Java

Every non-trivial program has at least one bug.


Corollary 1 - A sufficient 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 configuracin 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 significado 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 configurado
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 configurado 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://abulafia.fciencias.unam.mx/practicas.

El archivo build.xml
Ant utiliza un archivo de configuracin 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 final (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? Justifica 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? Justifica tu respuesta.

Prctica:
Usar y modificar
clases

Bugs will appear in one part of a working program when another


unrelated part is modified.
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 finalizar la prctica el alumno ser capaz de:

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);
modificar 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 modificaremos 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 modificar 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 definido 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 definido 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.

11

Usar y modificar clases

(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
<classpath>
<pathelement path="build" / >
< / classpath>

11
12
13

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

13

Usar y modificar 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.

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 .
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 finos 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).

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 significado en espaol.

15

Usar y modificar clases

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 definicin 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 definicin del mtodo avanzaHora; si vuelves
a dar C-s , XEmacs marcar ahora s la definicin 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 modificador 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),
5

Algunas veces tambin se utilizan funciones que no cambian el estado de un objeto, ni tampoco lo
obtienen; el mtodo espera es un ejemplo.

17

Usar y modificar clases

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 verifica 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. Definiremos 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. Especifica 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 fin 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.

19

Usar y modificar clases

Actividad 2.7 Invoca a JavaDoc sin parmetros con la siguiente lnea:


# javadoc
Anota todas las opciones que se podran utilizar.

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. Modifica 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 modificar el archivo ClaseReloj.java, y en
el segundo ejercicio slo debes modificar 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 grficas.

20

Preguntas
1. Entiendes cmo funciona el mtodo avanzaHora? Justifica tu respuesta.
2. Entiendes cmo funciona el mtodo ponTiempo? Justifica 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 finalizar 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 identificar los objetos que
representan nuestro problema, agruparlos en clases y definir precisamente el comportamiento (las funciones) que tendrn para que resuelvan nuestro problema.
La segunda parte es un poco ms detallada. Una vez definido 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

1
2
3
4
5
6
7
8

23

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 significa la lnea


1

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

Por ahora slo necesitamos saber que eso significa 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 configurado), y pones esto:
/* * jde * */

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 .

Como recordars de la prctica anterior, utilizamos el directorio src para poner nuestro cdigo fuente (y as tambin lo especifiacmos 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 final 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 significado 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 modificar nuestro
build.xml.

Variables, tipos y operadores

25

Actividad 3.4 Modifica 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" / >

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 modificamos nuestro build.xml.

Actividad 3.5 Modifica 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, modifica 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 figura 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 ;

Observa que estamos utilizando t para representar los espacios dentro de las cadenas.

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 figura 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 figura 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

29

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

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 tipificado, lo que
significa 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 difieren 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
float
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 flotante de 32 bits de precisin simple
Punto flotante 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

31

Variables, tipos y operadores

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 final, como en 1234L, y si se quiere un flotante hay que poner una F al final, 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 tipificacin 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 float . 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 tipificacin 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 dificultad 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
flotante de 32 bits.
Por la misma razn, el siguiente cdigo s es invlido:
2

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 suficiente para manejar estos lenguajes.
Java es el primer lenguaje de programacin en soportar nativamente Unicode.

32

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

El primero debe ser obvio; no podemos hacer que un punto flotante 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 definicin 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 definidas 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.

posfijo unario
posfijo unario
posfijo n-ario
posfijo unario
posfijo unario
unario prefijo
unario prefijo
unario prefijo
unario prefijo
unario prefijo
unario prefijo
unario prefijo
unario prefijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo

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

binario infijo

izquierda >>

binario infijo

izquierda >>>

binario infijo
binario infijo
binario infijo

izquierda <
izquierda <=
izquierda >

Operandos

Smbolo

Descripcin

[ ]
.
(<parmetros>)
<variable>++
<variable>
++<variable>
<variable>
+<expresin>
<expresin>

<expresin>
!<expresin>
new <constructor>
(<tipo>)<expresin>

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

35

Variables, tipos y operadores


Tabla 3.2

Operadores de Java
Contina de la pgina anterior

binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
ternario infijo

Asocia
tividad
izquierda
izquierda
izquierda
izquierda
izquierda
izquierda
izquierda
izquierda
izquierda
izquierda

binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo
binario infijo

derecha
derecha
derecha
derecha
derecha
derecha
derecha

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

binario infijo

derecha

<<=

binario infijo

derecha

>>>=

binario infijo
binario infijo
binario infijo

derecha
derecha
derecha

&=
^=
|=

Operandos

Smbolo

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 especfico; 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 float .


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 flotantes 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 fijndose 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 significativo 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

37

int a = 3 + 2 * 5;

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: prefijo y post fijo. 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 flotante.
2. Declara un float llamado x, y asgnale la literal 1F. Declara un float llamado y
y asgnale la literal 0.00000001F. Declara un tercer float 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.
5

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.

40

Preguntas
1. Crees que sea posible asignar el valor de un flotante 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 final 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 files 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 finalizar 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 significa
identificar 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 infinidad) 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 definamos 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
modificar cada uno de ellos (hacerlo ms rpido, ms entendible, que abarque
ms casos), y los dems componentes no tendrn que modificarse para ello. A
esto se le llama encapsulamiento.
Cuando resolvamos otro problema distinto, es posible que al definir sus componentes descubramos que uno de ellos ya lo habamos definido 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
1

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.

43

Interfaces y clases por dentro

problema ya est resuelto; la clase Consola ya est escrita, y ahora la utilizaremos


casi todo el curso, sin necesidad de modificarla 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 definidos. 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 define los mtodos y variables que son comunes a todos los objetos de cierto tipo. En la clase definimos 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
final; 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 ge t D e t er m i na nt e ( ) ;
}

Un poco ms adelante veremos porqu la definicin 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
modificada 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 flotante 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 modificar
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:

47

Interfaces y clases por dentro


x

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 firma 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 tipificacin 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:

49

Interfaces y clases por dentro

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 definamos 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:


a b
det
= ad bc.
c d
2

Con nuestra clase Matriz2x2 hemos estado usando mucho this para no confundirnos con los nombres de las variables. Pero cuando no haya conflicto 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.

51

Interfaces y clases por dentro

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.

Polimorfismo
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 definir 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 definir 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 polimorfismo.
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 definida 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 modificamos 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 polimorfismo 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 defini
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:

1
2
3

public double g e t D e t er m i na nt e ( ) {
return determinante ;
}

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 modificaciones, 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 prefijo 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 modificador 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 ) ;
}
}

57

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:
< j a v a classname="UsoMatrizCuadrada" f o r k ="true">

y la tarea javadoc quedara:


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

Modificadores 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 modificacin 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 modificamos directamente una variable de clase de nuestro objeto m al hacer
m. a = 1 0 ;

Podramos hacer la solemne promesa de nunca modificar 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 modificadores 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 modificada. 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
modificada. 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 modificado 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 especificado, 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 modificadores 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 significado 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, significa 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
modificarlas 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 justificacin se hace evidente en esta ltima funcin. Cada vez que modifiquemos un componente de la matriz, actualizaremos automticamente el valor del determinante. Con esto dejamos bien definido cmo ver y
cmo modificar 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 modificadores
Hay dos modificadores ms para las variables y mtodos de Java. El primero de
ellos es final . Una variable final 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 final. Las variables finales 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 final para definirlo, 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 final.
Tambin facilita la lectura del cdigo.
Otros lenguajes de programacin tienen constantes. Las variables finales de Java no
son constantes exactamente; pero pueden emular a las constantes de otros lenguajes.
Hay mtodos finales tambin, pero su significado lo veremos cuando veamos herencia.
El otro modificador 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 modifica, 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 final, 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 definirlas. 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 definido 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 t 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 definido 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, flotantes, 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

m u

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

63

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 ) ;

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 modific 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 modificarse. 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 modificar 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 lexicogrficamente, 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 calificados. 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 simplificado 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 suficiente 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. Modifica 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 modifica 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 ) {

67

Interfaces y clases por dentro


...

20

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 modificar 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

Direccin

Telfono

Juan Prez Garca

Avenida Siempre Viva # 40

55554466

Arturo Lpez Estrada

Calle de la abundancia # 12

55557733

Edgar Hernndez Levi

Oriente 110 # 14

55512112

Mara Garca Snchez

Avenida Insurgentes Sur # 512

56742391

Pedro Pramo Rulfo

Avenida Mxico Lindo # 23

54471499

Jos Arcadio Buenda

Macondo # 30

56230190

Florentino Ariza

Calle de la Clera # 11

55551221

Galio Bermdez

Stanos de Mxico # 45

55552112

Carlos Garca Vigil

La Repblica # 1

55554332

Eligio Garca Agusto

Ciudades Desiertas # 90

56344325

Para representar a todos los datos, utilizaremos una sola variable esttica de tipo cadena, llamada tabla. Los registros (o renglones) son de tamao fijo, 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 final, 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

69

Interfaces y clases por dentro

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? Justifica.
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 finalizar 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 definido 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 clasificar 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 flotante, 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 final 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 finales para etiquetar los case. Es el nico tipo
de variables que podemos usar: un case slo puede ser etiquetado por una variable final
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 flotantes 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 calificacin):
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." ) ;
}

75

Estructuras de control y listas

La relacin entre switch e if


El if es slo una particularizacin del switch (o el segundo una generalizacin del
primero, como prefieran). 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 refleja 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 final 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 flujo 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. Definiremos las listas como sigue

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

La definicin es recursiva; hace referencia a lo que est definiendo. Piensen en los


nmeros naturales y los axiomas de Peano; un nmero natural es 1, o un nmero natural
ms 1.
Por su misma definicin, 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 significa 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

81

Estructuras de control y listas

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

"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 final 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 especificar 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 calificado es icc1.interfaz.Consola. Igual, el nombre calificado de la clase ListaDeCadena es
icc1.util.ListaDeCadena. Ya hemos usado nombres calificados; cuando en el objetivo run
de nuestro build.xml especificbamos qu clase iba a ser ejecutada, siempre ponamos el
nombre calificado de la clase (recuerden icc1.practica1.UsoReloj).
Si no utilizramos la palabra clave import en nuestras clases, tendramos que usar
el nombre calificado 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 especificamos en el build.xml.
Cuando una clase no tqiene un paquete especificado, est en el paquete por omisin.
Todas las clases que no tienen paquete especificado 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 final;
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
jarfiles, 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 refleje 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? Justifica.
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? Justifica 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 finalizar 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 definir 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 definan 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 definir mtodos propios, la clase B puede redefinir 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

89

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 {
...

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 definido 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
definido 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 significa 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 calificador 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 firma de un mtodo. En el caso de Java, el tipo del valor que regresa el
mtodo no se considera como parte de la firma, 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 redefinir 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

91

Herencia

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 definir al mtodo como final:
public f i n a l void metodo ( ) {
...
}

Habamos dejado pendaiente la explicacin de qu era un mtodo final desde la


prctica 4. El significado es algo distinto a las variables finales; est nicamente relacionado con la herencia. Si un mtodo es final, ya no puede redefinirse 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 definirlo.
Dijimos un poco ms arriba que casi todas las clases podan extenderse. Podemos
evitar que una clase sea heredada si la hacemos final:
public f i n a l class ClaseNoHeredable {
...

Funciona de la misma manera que con los mtodos finales; si una clase es final,
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 define
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 defina 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 figura 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.

93

Herencia

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 definen 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 (especificidad) 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 significara
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 definidos como quisiramos).

Los once mtodos de la clase Object tienen un significado 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 identificar 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 definamos).

95

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 ; .

La clase Lista puede tener como elementos objetos de cualquier clase. Esto en particular significa 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, float , 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 floatValue. 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 tipificacin 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 ( ) ;

97

Herencia

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 float 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 definir 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 finales. 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.

99

Herencia

La herencia en el diseo
La herencia modifica significativamente 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 definimos 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 redefiniciones) 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
lexicogrficamente.
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 definir mtodos para tener acceso y cambiar las variables de clase (las primeras acostmbrate a ponerles prefijo 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).

101

Herencia

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. Redefine 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 definido
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 afirmativa 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 finalizar esta prctica el alumno ser capaz de:
entender lo que es entrada y salida;
entender lo que es un flujo (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

105

Entrada/salida y arreglos

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 flujo (stream en ingls), haciendo referencia a un flujo de agua. Hay flujos de los que podemos determinar fcilmente dnde
empiezan y dnde terminan (como los archivos en un disco duro o un CD-ROM) y hay
flujos que no tienen un principio o un fin determinados (como las estaciones de radio
en Internet, que mandan bytes ininterrumpidamente con la seal de la estacin).
Vindolos como flujos, hay flujos de entrada, es decir, de los que recibimos bytes,
y flujos de salida, o sea a los que les mandamos bytes. Un archivo en un CD-ROM
es un flujo de entrada (no podemos mandarle informacin, slo obtenerla). Cuando
guardamos un archivo en XEmacs, utilizamos un flujo de salida al mandar informacin
al disco duro. Pine o GNUS o en general cualquier lector de correo electrnico, utiliza
flujos de entrada para recibir correos de su servidor y flujos de salida para mandarle al
servidor de correo.
Todos los flujos funcionan de la siguiente manera:
1.
2.
3.
4.

Se crea el flujo (se abre el archivo en disco, se hace la conexin en red, etc.)
Si el flujo es de entrada, se lee de l.
Si el flujo es de salida, se escribe en l.
Al terminar, se cierra el flujo. Esto es importante, ya que generalmente un flujo
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 flujos que pueden abrirse simultneamente para lectura y escritura. Sin embargo, esto no necesariamente representa una gran ventaja y no veremos ese tipo de flujos
en la prctica.

Filtros
Podemos conectar los flujos con programas o funciones, de tal manera que se reciban bytes por un flujo de entrada, el programa o funcin le haga algo a estos bytes, y
se vuelvan a enviar a algn otro lado utilizando un flujo de salida. A estos programas o
funciones se les denomina filtros.
Un filtro 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 filtro 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 filtro. 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, flotantes, 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
flujos 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 float , 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 flujo de salida (un archivo en disco duro por ejemplo), tenemos que convertirlas a
bytes y entonces mandarla por el flujo. Si queremos guardar un int , tenemos que hacer
lo mismo. Y si luego queremos utilizar un flujo de entrada para leer los datos, tenemos
que hacer el camino a la inversa.
1

Con computadoras cada vez ms veloces, y compiladores cada vez ms inteligentes, esto es cada
vez menos perceptible.

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

(12);
(1.4F ) ;
( "hola mundo" ) ;
( true ) ;

sad . c i e r r a ( ) ;

obtendr un archivo de texto llamado archivo.txt que puede ser modificado 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

109

Entrada/salida y arreglos

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 flujo es un


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

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 flujos, 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 final 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 calificados 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 definido 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 :

111

Entrada/salida y arreglos

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
final 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 final 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.
2

El nico caso en que Java permite la asignacin al vuelo de un valor a una variable es con cadenas
(String).

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 flotante 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.

115

Entrada/salida y arreglos

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 firma:
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 modificacin 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 flujo 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 firma:
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 modifica de manera alguna. De hecho, el objeto con el

117

Entrada/salida y arreglos

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
firma:
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, modifica 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 flujo 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 flujo 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? Justifica.
2. Crees que sera mejor si la base de datos utilizara un arreglo en lugar de una
lista? Justifica.

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 finalizar 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 definida 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 codificamos 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.

123

Recursin

Si una funcin recursiva no tiene clusula de escape o est mal definida, 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 infinito.
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 simplificar el cdigo de manera significativa.
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 figura 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.
1

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.

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?

125

Recursin

Actividad 8.1 Baja el archivo Jar hanoi.jar de la pgina de las prcticas. El archivo
contiene una muestra grfica 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.

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 fichas 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 definicin de factorial y de muchos algoritmos que naturalmente se definen 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 definicin (recuerda que es una definicin 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.

127

Recursin

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? Justifica 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 finalizar 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 significa 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 (finally), 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 ... finally .

try ... catch ... finally


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 fin 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.
1

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.

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 finally (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 finally es opcional; no es obligatorio que aparezca. Pero debe quedar en
claro que cuando aparece, el bloque finally se ejecuta incondicionalmente, se lance o
no se lance ninguna excepcin. La nica manera de impedir que un finally se ejecute
es terminar la ejecucin del programa dentro del catch (lo que no tiene mucho sentido
si existe un bloque finally ).
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 fino 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 final, 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 ... finally .
La segunda es encadenar el lanzamiento.
Encadenar el lanzamiento significa 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 fila 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 fila (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 especfico 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 especificarlo 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 ... finally (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 suficientemente 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 definicin 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
definir qu excepciones puede lanzar una clase, o al momento de escribir los bloques
try ... catch ... finally .
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 especifique 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.

137

Manejo de excepciones

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 flujo 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 flujo 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 fin 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 ... finally . 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 especifiquen 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 final 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 ... finally 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

141

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 ;

2. En la clase BaseDeDatos, modifica 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 modificadas las funciones, tendrs que modificar la clase de uso, ya que
se necesitar atrapar las excepciones para que compile la clase de nuevo.
3. Cuando modifiques las funciones agregaRegistro y borraRegistro, modifica los comentarios de JavaDoc utilizando la etiqueta @throws para especificar qu excepciones lanza y cundo las lanza. Tambin comenta las clases de tus excepciones.

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

Prctica:
Interfaces grficas

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 grficas.

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

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 grficas de usuario, IGUs, o GUIs por las mismas siglas en
ingls.
(El hecho de que las interfaces grficas nos compliquen la vida a los programadores
no parece importarle mucho a los usuarios. . . )
A lo largo de estas prcticas hemos utilizado interfaces grficas sencillas, aunque
ustedes mismos no las han programado. En esta prctica veremos cmo se programan
las interfaces grficas 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 grficas. Las ltimas versiones utilizan las clases del paquete javax.swing, que
ser el definitivo 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 grfica en Java est compuesta de componentes alojados en un contenedor. Veamos una pequea interfaz grfica 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 figura 10.1

145

Interfaces grficas
Figura 10.1

Dilogo de leeString

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 grfica 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 figura 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 grfica, 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 especificar 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.

147

Interfaces grficas

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 grficas. 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 definirse un administrador de trazado [21]; en este caso es de la clase GridLayout, lo que significa 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 defina 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

149

Interfaces grficas

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 (modificando 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 modifica 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 }

151

Interfaces grficas

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 modificar 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 {
...
1

Por supuesto, a menos que el escucha slo tenga un mtodo. No tendra sentido tener un adaptador
en ese caso.

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
grfica.
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 grfica. 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

153

Interfaces grficas

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 ay 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 modificar 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 finales, 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 finales. En el caso de la etiqueta
realmente no importa ya que no modificamos la referencia al objeto (cambiamos su
contenido, pero la referencia sigue siendo la misma). En el caso del contador queremos
que se modifique en cada llamada, y entonces no podemos usar una variable de tipo
int , ya que tiene que ser final. Por eso usamos un arreglo; de nuevo, no modificamos 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 grficas

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 grficas, pero podemos usarlas cundo y dnde queramos. Sin embargo nosotros slo las
utilizaremos para interfaces grficas (y de hecho es lo recomendable).

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

Los componentes de Swing


Swing ofrece varios componentes (herederos casi todos de la clase JComponent)
para crear prcticamente cualquier tipo de interfaz grfica.
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 grficas

157

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.

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 flexible 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 definir 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 fijo 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 especificar 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 grficas.
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.

159

Interfaces grficas

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 flexible 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 grficas 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 grfica 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 modificado.
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 figura 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

161

Interfaces grficas

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

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 ;

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 .

import
import
import
import

java .
java .
java .
java .

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

/ / 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 .

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 .

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 modificarlo de manera sencilla
en todos nuestros mtodos. La etiqueta para decirle al usuario si se ha modificado 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 modificado. Vamos adems a aadir
otra cadena para guardar el nombre del archivo (vamos a necesitarlo despus) y definamos 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

estado ; / / E l estado d e l a r c h i v o .

private String

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 ;

private String
p r i v a t e boolean

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

/ / 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 .

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 modificado [76], inicializa las variables con el nombre del archivo [77] (que puede ser null),

163

Interfaces grficas

y manda crear la ventana principal [78]. Para esto suponemos que tendremos una funcin abrirArchivoDeDisco que abrir el archivo especificado 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 definimos 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 prefijo 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:

165

Interfaces grficas

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 GraphicsConfiguration, pero ah va una explicacin


rpida: se crea un rectngulo [129], obtenemos el ambiente grfico [131], del cual sacamos los dispositivos grficos disponibles [132], a cada uno de los cuales les sacamos
la configuracin [135], y de todas las configuraciones hacemos una unin [137], para
al final 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 significa 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 modificado [153167] (cambiamos la variable modificado a true, y ponemos la cadena
"Modificado" en la etiqueta estado). Esto tiene sentido, ya que los tres eventos
implican modificar 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

167

Interfaces grficas

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 significa 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 modificador 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].
2

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.

169

Interfaces grficas

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 verificar que el archivo en la ventana no est


modificado [249]. Si est modificado, entonces hay que confirmar si se deja o no
el archivo [250]; para eso suponemos un mtodo confirmarDejarArchivo.
Despus limpiamos el texto del componente de texto [252], y dejamos el estado
del editor como no modificado [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 modificado [265] y si es as pide confirmacin 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

171

Interfaces grficas

[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
modificado [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 significa 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 ) ;
}

173

Interfaces grficas

Si el archivo est modificado [355], pedimos confirmacin 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 definimos
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 modificado [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.

175

Interfaces grficas

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 modificado [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.

confirmarDejarArchivo
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 modificado 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, define 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 grficas

177

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

Observaciones finales
Hay que reflexionar 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 grficas 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 grficas (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 grficas (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" ) ) ;

define 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 definidos
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 grfica 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.

179

Interfaces grficas

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 modificacin.
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 grficas? Explcate.

Prctica:
Ant y archivos Jar

11

Any program will expand to fill 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 finalizar 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 suficiente 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

183

# javac Matriz2x2.java

El compilador de Java es lo suficientemente 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 suficiente. 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 simplificarse:
# 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 significa que necesitamos de la ruta de clases (classpath).
1

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

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 especificarle 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 especificar 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 ...

185

Ant y archivos Jar

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 modificarlo, 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 modificar 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
modificar 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 suficiente 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.

187

Ant y archivos Jar

Actividad 11.1 Consulta la documentacin de Ant en su pgina en


<http://ant.apache.org>.

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 eficiente.
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 (jarfiles en ingls) se llaman as por Java Archives o archivos de Java. Aqu se entiende archivo como fichero en el sentido bibliogrfico, 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.

189

Ant y archivos Jar

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 especificamos 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 manifiesto (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 manifiesto.

Ejercicios
1. Como vimos, para hacer un archivo Jar ejecutable directamente por la mquina
virtual necesitamos incluirle un archivo manifiesto. 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 manifiesto 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 manifiesto
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 especificar 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 especificar 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 finalizar 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 fin 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 grfica 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 grfica 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
1

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.

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 grficos de Java es un ciclo infinito, 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 grfica (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 grficas 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:

195

*Hilos de ejecucin y enchufes

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 ();

/ / 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 .

/ / I n s t r u c c i n 2
}

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 grficamente como en la figura 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 especfica, 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 definir 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 GraficosDeReloj 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 confiable 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 filsofos sentados alrededor de una mesa circular. Enfrente de cada filsofo hay un tazn de arroz y a la derecha
de cada filsofo hay un palillo.
Para comer un bocado de arroz, cada filsofo necesita el palillo que le corresponde
y adems el palillo a su izquierda (que le toca a otro filsofo). Si todos los filsofos
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 ejemplificar 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 filsofos 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 definicin 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 filsofos podemos hacer que los
filsofos 2 y 4 esperen a que los filsofos 1 y 3 hayan usado los palillos para que ellos
los usen; y que el filsofo 5 espere a los filsofos 2 y 4; y por ltimo que los filsofos 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 definido 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 definidas 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 definido 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 especifique a qu grupo
pertenece se asigna al grupo de hilos de ejecucin por omisin.
2

sta es una solucin que garantiza que todos los filsofos comen, pero es ineficiente porque mientras
el filsofo 5 come podra hacerlo tambin algn otro filsofo. Hay muchas soluciones distintas para el
problema de los filsofos y los palillos.

*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 especfica 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
definimos. 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 significa Java Servlet Page y funciona muy similarmente a
PHP o las pginas ASP de Microsoft (PHP significa 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

203

Actividad 12.2 Consulta la documentacin de los paquetes java.net, java.rmi, y


org.omg.CORBA.

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 grfica. 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 infinito de enchufes


conectados a un mismo puerto; sin embargo siempre se limita a un nmero fijo 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 grfica, 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 especificar 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? Justifica 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 infinite in variety, in contrast to detectable errors, which by definition 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 find 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

==, 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

<<=, 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 finales, 91
clases fbrica, 60
clases internas, 152157
comentarios, 1718
comentarios para JavaDoc, 6667
componentes grficos, 144157
adaptadores, 150151
administradores de trazado, 146

212

NDICE ALFABTICO
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

encapsulamiento, 42
interfaces, 84
enchufes, 204208
cliente, 205, 207
servidor, 205207
excepciones, 130138
bloque catch, 130
bloque finally, 130
bloque try, 130
lanzar excepciones, 130
manejadores de excepcin, 132
expresiones, 3839
extends, 88, 89, 98
false, 31, 33, 35, 53, 74
filtros, 106107
flujos, 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
jarfiles, 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
firma de un mtodo, 90
mdotos finales, 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

213
pila de ejecucin, 130, 134
polimorfismo, 5153
portabilidad, 3
recolector de basura, 6465
conteo de referencias, 65
recursin, 122126
caer en loop, 123
clusula de escape, 122
definicin 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
this, 48, 50
tipos, 2932
fuerte tipificacin, 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 finales, 60
variables locales, 15, 29, 49
vida de una variable, 45
while, 7678, 80

214

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

También podría gustarte