Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
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>
ndice general
1. Ant y el compilador de Java
21
41
71
6. Herencia
87
7. Entrada/salida y arreglos
103
8. Recursin
121
9. Manejo de excepciones
129
143
181
191
209
Prctica:
Ant y el
compilador de
Java
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.
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.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.
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
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.
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>
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
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
6
# ant run
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
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
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:
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.
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.
< 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
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
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 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
(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).
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
Reloj r e l ;
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 ( ) ;
13
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 ( ) ;
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
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 ;
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.
t h i s . hora++
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
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
Interfaces
Hemos estado trabajando con la clase ClaseRejoj; sin embargo, el objeto que declaramos fue de la interfaz Reloj. Qu es una interfaz?
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 ) . * /
/
*
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
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
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
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.
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 ) {
}
}
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 ;
25
por
6
7
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.
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." ) ;
}
}
Consola c ;
Observa que estamos utilizando t para representar los espacios dentro de las cadenas.
27
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
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." ) ;
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." ) ;
o incluso
13
La clase Consola ofrece muchos mtodos, que iremos utilizando a lo largo de las
dems prcticas. En sta slo veremos imprime e imprimeln.
29
Actividad 3.7 Crea la clase Prueba en el archivo Prueba.java y realiza (valga la redundancia) pruebas con imprime e imprimeln.
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
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
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)
entero ;
doble ;
caracter ;
booleano ;
Contina en la siguiente pgina
31
=
=
=
=
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 !
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.
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 ;
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
*
/
%
+
<<
35
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
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;
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
37
int a = 3 + 2 * 5;
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 ++;
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 )
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.
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
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;
Prctica:
Interfaces y clases
por dentro
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
<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>
}
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 ( ) ;
}
45
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
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
x
a b
c d
=
xa xb
xc xd
.
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
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 ) ;
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
MatrizCuadrada m2;
double w = 3 . 8 ;
m2 = m. m u l t i p l i c a (w ) ;
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
=
=
=
=
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
(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
=
=
=
=
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 ;
}
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
/ / 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
=
=
=
=
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 ;
}
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).
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
1
2
3
public double g e t D e t er m i na nt e ( ) {
return determinante ;
}
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.
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 }
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
/ / Podemos pasar a l a m a t r i z d i r e c t a m e n t e .
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
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">
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
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 ;
59
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
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
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 !
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:
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 ( ) ;
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 ) ;
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 ) ;
La clase String de Java ofrece muchas otras funciones tiles, que iremos aprendiendo
a usar a lo largo de estas prcticas.
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 ) ;
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;
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
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
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 ) {
...
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
55554466
Calle de la abundancia # 12
55557733
Oriente 110 # 14
55512112
56742391
54471499
Macondo # 30
56230190
Florentino Ariza
Calle de la Clera # 11
55551221
Galio Bermdez
Stanos de Mxico # 45
55552112
La Repblica # 1
55554332
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 ) ;
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
23
54471499"
Y lo mismo si hacemos
busqueda = bdda . dameRegistroPorTelefono ( 5 4 4 7 1 4 9 9 ) ;
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
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;
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 ( ) ;
...
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 .
75
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.
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 ++;
}
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 ) ;
}
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.
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 ( ) ) ;
}
}
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 ++;
}
81
algn elemento; y entonces romper el while con un break o con un return. Por supuesto,
tambin funciona con un do ... while o con un for.
Hemos trabajado con una funcin imaginaria llenaLaLista, pero para crear una lista
con un nico elemento solamente tendremos que usar new.
ListaDeCadena a ;
a = new ListaDeCadena ( "una cadena" ) ;
"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" ) ;
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
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 ;
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
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.
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 ) ;
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
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 ;
}
}
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.
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.
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.
97
Herencia
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 {
...
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
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 ( ) ;
...
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 ( ) ;
...
105
Entrada/salida y arreglos
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).
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.
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.
(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 ( ) ;
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:
#
#
#
#
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." ) ;
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);
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 ] ;
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
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" } ;
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 .
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
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
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 ) {
...
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 ) {
...
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
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
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.
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 ;
}
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:
/* *
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.
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
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 ) ;
}
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
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 .
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:
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" ) ) ;
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 ( ) ;
}
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.
134
La clase Throwable
La sintaxis de un manejador de excepcin es
catch ( < AlgunaClaseHerederaDeThrowable > t ) {
...
}
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 {
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 { }
137
Manejo de excepciones
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
Manejo de excepciones
139
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 ;
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
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:" ) ;
145
Interfaces grficas
Figura 10.1
Dilogo de leeString
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 ;
148
Contina de la pgina anterior
22
23
24
25
26
27
28
29
30
}
31 }
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.
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
(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
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.
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
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
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 ;
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 ;
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.
Actividad 10.6 Consulta la documentacin de la clase JComponent en la pgina referida para ello.
Interfaces grficas
157
Contenedores intermedios
Los contenedores intermedios nos sirven para agrupar y presentar de cierta manera
distintos componentes. Mientras que se pueden realizar varias cosas con ellos, aqu
slo daremos una breve descripcin de cada uno de ellos y nos concentraremos en los
objetos de la clase JPanel en el ejemplo ms abajo.
Panel. Es el contenedor intermedio ms 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.
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
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 .
161
Interfaces grficas
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
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 .
/ / 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
/ / 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].
164
Contina de la pgina anterior
114
115
116
117
118
119
120
121
122
123
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 ;
}
166
Contina de la pgina anterior
163
164
165
166
167
168
169
167
Interfaces grficas
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
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
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 ,
171
Interfaces grficas
menuSalvarArchivo
302
303
304
305
306
307
308
309
310
311
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
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
173
Interfaces grficas
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).
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 ) ;
}
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 ,
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
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
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
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
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:
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
184
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
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
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
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.
<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
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.
Tambin podemos crear archivos Jar desde nuestro build.xml. Slo utilizamos la tarea
<jar/>; por ejemplo, igual para nuestra prctica 10:
50
51
52
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
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
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
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
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.
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 ) ;
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
}
195
1. Se ejecutaba la instruccin 1.
2. Se llamaba al mtodo B (ejecutndose todas las instrucciones, ciclos, recursiones,
etc. del mtodo).
3. Se ejecutaba la instruccin 2.
Con un hilo de ejecucin, en cambio, tenemos esto (si suponemos que tenemos un
hilo de ejecucin llamado t):
{
/ / I n s t r u c c i n 1
t . start ();
/ / 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.
196
{
( 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 .
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
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 .
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 .
}
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-
199
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.
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.
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.
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.
203
Enchufes
Los enchufes, contrario a lo que pudiera pensarse, funcionan como enchufes. Piensen en el enchufe telefnico; es un punto de entrada/salida al exterior. Pueden recibir
informacin a travs de l (cuando escuchan), y mandar informacin a travs de l
(cuando hablan). De hecho, pueden hablar y or al mismo tiempo; pueden recibir y
enviar informacin al mismo tiempo.
La idea de los enchufes es crear puntos de entrada/salida entre dos computadoras;
una vez creados, las computadoras podrn enviarse bytes mutuamente a travs de ellos.
Es importante sealar que la comunicacin se reduce a bytes, por lo que son de muy
bajo nivel.
Para establecer la conexin entre dos enchufes, se necesitan dos programas. Se podra hacer la conexin con un solo programa utilizando hilos de ejecucin, pero no tiene
sentido ya que lo que queremos es comunicar dos mquinas.
El primer programa se llama servidor y lo que hace es estar escuchando en un puerto
de comunicacin de la mquina donde est corriendo, esperando por una solicitud de
conexin. Cuando la recibe crea un enchufe, y si la solicitud es vlida, la comunicacin
queda establecida.
El segundo programa se llama cliente, y lo que hace es crear un enchufe con la
direccin de la mquina y el puerto donde est escuchando el servidor. Si el servidor
est en esa mquina escuchando en ese puerto, la conexin queda establecida.
Una vez que la conexin ha sido establecida, cada enchufe dispone de un objeto
de la clase InputStream y de otro objeto de la clase OutputStream. Cuando un enchufe
manda bytes a su OutputStream, el otro los recibe por su InputStream y viceversa. El
control de los enchufes queda totalmente en manos del programador. De acuerdo a la
aplicacin se ver cmo cada enchufe controla los mensajes que manda y recibe.
El objeto de la clase InputStream de los enchufes tiene implementado el mtodo
available, por lo que siempre podemos saber si hay algo que leer de l. Si el metodo
available regresa un entero mayor que cero, entonces hay algo que leer. Si regresa cero
no hay nada que leer.
El servidor
Lo que hace un servidor se resume en las siguientes lneas:
204
try {
i n t p u e r t o = 10000;
ServerSocket s e r v i d o r = new ServerSocket ( p u e r t o ) ;
Socket c l i e n t e = s e r v i d o r . accept ( ) ; / / Lo ponemos a escuchar .
m an ej a Cl i en t e ( c l i e n t e ) ;
} catch ( E x c e p t i o n e ) {
/ * Crear un enchufe de s e r v i d o r y p o n e r l o a escuchar es
p o t e n c i a l m e n t e p e l i g r o s o y puede r e s u l t a r en que sean
lanzadas v a r i a s excepciones . * /
}
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.
205
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 . * /
}
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
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.
Ejercicios
1. Basndote (si quieres) en el servidor que slo recibe una conexin, utiliza hilos
de ejecucin para que pueda recibir varias conexiones.
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
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