Sunteți pe pagina 1din 544

Introducción

1
En este capı́tulo introducimos conceptos relacionados con la compu-
tación; presentamos una breve historia de las computadoras y del pro-
ceso de abstracción que ha llevado a conformar la disciplina de las
ciencias de la computación como la conocemos hoy en dı́a. Asimismo
revisamos la manera como han evolucionado los equipos de cómputo
hasta alcanzar las formas que tienen actualmente.

1.1 Significado y alcances de la computación

La disciplina de la computación es el estudio sistemático de proce-


sos algorı́tmicos que describen y transforman información: su teorı́a,
análisis, diseño, eficiencia, implementación y aplicación. La pregunta
fundamental subyacente en toda la computación es, “¿Qué puede ser
(eficientemente) automatizado?”
Peter Denning, 2005.

Hay mucha confusión respecto a términos que, aparentemente describen a la


misma disciplina. Usaremos a lo largo de este texto los términos computación y
ciencias de la computación casi indistintamente. Es necesario recalcar que estamos
usando el término computación como abreviatura para ciencias de la computación,
1.2 Conceptos generales 2

con el significado particular que le estamos dando a este último en nuestro con-
texto. El error más común es el de confundir la programación con la computación.
La diferencia que existe entre estos dos términos es tal vez la misma que existe
entre saber la fórmula para resolver una ecuación de segundo grado y conocer la
teorı́a de ecuaciones. Si bien la programación es una parte de la computación,
la computación contempla muchı́simos otros aspectos que no forzosamente tienen
que ver con la programación o llevarse a cabo con una computadora. También se
utilizan los términos de ingenierı́a y ciencias de la computación y, excepto por el
enfoque que se pudiera dar en uno u otro caso, estarı́amos hablando del mismo
cuerpo de conocimientos.
Otro término que se utiliza frecuentemente (sobre todo en nuestro medio) es
el de informática. Si bien en muchos casos se utiliza este término para referirse
a todo lo que tiene que ver con computación, nosotros lo entendemos más bien
como refiriéndose a aquellos aspectos de la computación que tienen que ver con
la administración de la información (sistemas de información, bases de datos,
etcétera). Al igual que la programación, la informática la podemos considerar
contenida propiamente en la computación, con aspectos de administración no
exclusivos de la computación.
El término cibernética es un término forjado por los soviéticos en los años
cincuenta. Sus raı́ces vienen de combinar aspectos biológicos de los seres vivos
con ingenierı́a mecánica, como es el caso de los robots, la percepción remota, la
simulación de funciones del cuerpo, entre otros. A pesar de que se utiliza muchas
veces en un sentido más general, acá no lo haremos ası́.
Por último, un término que ha tomado relevancia en los últimos años es el de
equiparar las ciencias de la computación con lo que se denomina las tecnologı́as de
la información, en cierto sentido tomando en cuenta que, como dice Denning, las
ciencias de la computación tienen que ver con el manejo de información codificada
de cierta manera. Este término es mucho más cercano a informática que a ciencias
de la computación, pues se preocupa de formas de manejar, transmitir y procesar
la información sin asomarse demasiado a las implementaciones de bajo nivel.

1.2 Conceptos generales

El objeto fundamental de estudio de las ciencias de la computación son los


algoritmos y, en su caso, su implementación. Veamos antes que nada la definición
de algoritmo:
3 Introducción

Definición 1.1 Un algoritmo es un método de solución para un problema que cumple con:
1. Trabaja a partir de 0 o más datos (entrada).
2. Produce al menos un resultado (salida).
3. Está especificado mediante un número finito de pasos (finitud).
4. Cada paso es susceptible de ser realizado por una persona con papel y lápiz
(definición).
5. El seguir el algoritmo (la ejecución del algoritmo) lleva un tiempo finito
(terminación).
Estamos entonces preocupados en ciencias de la computación por resolver pro-
blemas; pero no cualquier problema, sino únicamente aquéllos para los que poda-
mos proporcionar un método de solución que sea un algoritmo –aunque en realidad
uno de las temas más importantes en computación, dado que está demostrado que
hay más problemas que soluciones, es el de discernir cuando un problema tiene
solución, y si la tiene, si es una solución algorı́tmica–.
La segunda parte importante de nuestra disciplina es la implementación de
algoritmos. Con esto queremos decir el poder llevar a cabo de manera automática
un algoritmo dado (o diseñado).
En la sección que sigue exploraremos la historia de estos dos conceptos y
la manera en que se distinguieron para conformar lo que hoy conocemos como
ciencias de la computación.

1.3 Breve historia del concepto de algoritmo

La computación es una disciplina muy antigua. Tiene dos raı́ces fundamentales:

La búsqueda de una sistematización del pensamiento, que dicho en nuestro


terreno se interpreta como la búsqueda de algoritmos para resolver proble-
mas, algunas veces generales y otras concretos.
La búsqueda para desarrollar implementos o medios que permitan realizar
cálculos de manera precisa y eficiente.

En esta segunda raı́z podemos reconocer los implementos de cómputo como


el ábaco chino y el ábaco japonés, las tablillas de los egipcios para llevar la con-
tabilidad de las parcelas cultivadas y, en general, todos aquellos mecanismos que
1.3 Historia 4

permitı́an de manera más expedita, conocer un cierto valor o llevar a cabo un


cálculo.
Respecto a la búsqueda de la sistematización del pensamiento, en la Antigua
Grecia se hizo una contribución enorme en esta dirección con el desarrollo del
método axiomático en matemáticas y el desarrollo de la geometrı́a como un sis-
tema lógico deductivo, en el que juegan un papel importante el modus ponens
(pA Ñ B ^ Aq Ñ B) y el modus tollens (pA Ñ B ^ B q Ñ A). Lo que se
pretende con estos mecanismos es, a partir de un conjunto de hechos aceptados
(axiomas) y mediante un cierto conjunto de reglas del juego (reglas de inferencia
o de transformación) lograr determinar la validez de nuevos hechos. En el fondo
lo que se buscaba era un algoritmo que describiera o automatizara la manera en
que los seres humanos llegamos a conclusiones. Estos patrones de razonamiento
se utilizan no sólo en matemáticas, sino en la vida diaria y han dado origen a
una disciplina muy extensa y rigurosa que es la lógica matemática. No creemos
necesario remarcar el gran avance que ha tenido esta disciplina en la última mi-
tad del siglo XX. Sin embargo, es importante notar que el desarrollo ha estado
siempre presente, no únicamente en este último perı́odo. Para ello presentamos
a continuación una tabla con aquellos eventos históricos que consideramos más
relevantes en el desarrollo de la computación, con una pequeña anotación de cuál
fue la aportación en cada uno de los incisos –los números romanos corresponden
a siglos–.

Tabla 1.1 Resumen de la historia de Ciencias de la computación (1/3)

2000 AC Babilonios y egipcios Tablas para cálculos aritméticos, como raı́ces


cuadradas, interés compuesto, área de un
cı́rculo (8/9*1/2=3.1604...).
IV AC Aristóteles (384-322 AC) Lógica formal con Modus Ponens y Modus
Tollens.
825 Abu Ja’far Mohammed ibn Libro sobre “recetas” o métodos para hacer
Müsa al-Khowärizmı̀ aritmética con los números arábigos.
1580 François Viète (1540-1603) Uso de letras para las incógnitas: surgimiento
del álgebra.
1614 John Napier (1550-1617) Huesos de Napier para la multiplicación. El
concepto de logaritmo.
1620 Edmund Gunter Primer antecesor de la regla de cálculo.
(1581-1626)
XVI y XVII Galileo (1564-1642) Formulación matemática de la Fı́sica.
5 Introducción

Tabla 1.1 Resumen de la historia de Ciencias de la computación (2/3)


XVI y XVII Descartes (1596-1650) Descubre la geometrı́a analı́tica, posibilitan-
do la aplicación del álgebra a problemas
geométricos y, por lo tanto, a problemas que
tenı́an que ver con movimiento fı́sico.
1623 Wilhelm Schickard Primera calculadora digital: suma y resta au-
tomáticamente; multiplica y divide en forma
semiautomática.
1642-1644 Blaise Pascal (1623-1662) Calculadora que sobrevivió. Sólo sumaba y
restaba automáticamente.
XVII y Gottfried Wilhelm Leibniz Coinventor del cálculo con Newton. Primer
XVIII (1646-1717) investigador occidental de la aritmética bi-
naria. Inventor de la “rueda de Leibniz”,
que hacı́a las cuatro operaciones aritméticas.
Fundamentos de la lógica simbólica.
XIX Charles Babbage Máquina Diferencial y Máquina Analı́tica.
(1791-1871)
1842 Ada Lovelace Primera persona en programar la Máquina
Analı́tica de Babbage.
1854 Pehr George Scheutz Construyó un modelo de la Máquina Diferen-
cial de Babbage.
1854 George Boole (1815-1864) Estableció los fundamentos para el estudio
moderno de la Lógica Formal.
1890 Herman Hollerith Uso de equipo tabulador (de “registro
unitario”).
1893 Leonardo Torres y Quevedo Máquina electromecánica basada en la de
(1852-1936) Babbage.
1900 David Hilbert (1862-1943) Propuso a los matemáticos encontrar un sis-
tema de axiomas lógico–matemático único
para todas las áreas de la matemática.
1928 Hollerith Elaboración de tablas de posición de la luna
utilizando su máquina de registro unitario:
uso cientı́fico de herramientas pensadas para
procesamiento de datos.
1.4 Sistemas numéricos 6

Tabla 1.1 Resumen de la historia de Ciencias de la computación (3/3)

1936 Kurt Gödel (1906-1978) Demostró que lo que propuso Hilbert no es


posible, esto es que hay problemas matemáti-
cos inherentemente insolubles.
1936 Alan Turing (1912-1954) Atacó el problema de cuándo se puede de-
cir que se tiene un método de solución, que
el problema no tiene solución, etcétera. Di-
señó un implemento abstracto de cómputo
conocido a partir de entonces como la Máqui-
na de Turing.
1939-1945 Wallace J. Eckert con John Extensión de la máquina tabuladora de IBM
W. Mauchly para propósitos cientı́ficos. Diseño y cons-
trucción de la ENIAC, primera gran compu-
tadora digital totalmente electrónica.
1937-1942 Claude Shannon El uso del álgebra booleana para el análisis
de circuitos electrónicos. Liga entre la teorı́a
y el diseño.
1940 John V. Atanasoff (1903-) Solución de ecuaciones lineales simultáneas.
Computadora ABC.
1937-1944 Howard T. Aiken Construcción de la MARK I que tenı́a:
(1900-1937) Posibilidad de manejar números
positivos y negativos
Posibilidad de utilizar diversas
funciones matemáticas
Totalmente automática
Posibilidad de ejecutar operaciones
largas en su orden natural

1943-1945 John W. Mauchly ENIAC. Primera computadora totalmente


electrónica
1944 John von Neumann Notación para describir la circuiterı́a de la
computadora. Conjunto de instrucciones pa-
ra la EDVAC. El concepto de programa al-
macenado –la noción de que los datos y los
programas pueden compartir el almacenaje–.
Aritmética binaria como mecanismo en las
computadoras
7 Introducción

1.4 Sistemas numéricos

A lo largo de esta historia se han transformado notablemente los sı́mbolos que


se usaron para denotar a los objetos de los cálculos, a los algoritmos y a los re-
sultados mismos. Podemos pensar, por ejemplo, en que una sumadora de las de
Shickard usaba engranes como sı́mbolos para realizar las cuentas. El hombre pri-
mitivo usaba notación “unaria” para contar: ponı́a tantos sı́mbolos como objetos
deseaba contar. Una primera abreviatura a estos métodos se dio con sistemas que
agrupaban sı́mbolos. Ası́ por ejemplo, el sistema de números romanos combina
sı́mbolos para abreviar: en lugar de escribir 100 rayas verticales utiliza el sı́mbolo
“C”. Las abreviaturas del sistema romano son:
Número Sı́mbolo
de marcas romano
1 I
5 V
10 X
50 L
100 C
500 D
1000 M

El sistema numérico romano tiene reglas para representar cantidades distintas que
éstas y que son:
1. El valor total de un número romano es la suma de los valores de cada uno
de los sı́mbolos que aparecen en el número, menos la suma de los valores de
los sı́mbolos que estén restando.
2. Un sı́mbolo está restando si es que aparece inmediatamente a la izquierda de
un sı́mbolo con valor mayor que él. Pueden aparecer restando únicamente los
sı́mbolos I, X y C. No puede aparecer más de un mismo sı́mbolo restando.
3. Se pueden repetir hasta tres veces los sı́mbolos para el I, X, C y M.
4. Cuando un sı́mbolo está restando, sólo puede hacerlo de los dos sı́mbolos
que le siguen en valor:
I puede restarse de V y X.
X puede restarse de L y C.
C puede restarse de D y M.
Veamos algunos ejemplos:
1.4 Sistemas numéricos 8

IV representa el valor 4
IX representa el valor 9
IC representa el valor 99 ¡incorrecto!
XCIX representa el valor 99
XC representa el valor 90
XL representa el valor 40
XLIX representa el valor 49
IL representa el valor 49 ¡incorrecto!
CMXCIX representa el valor 999
IM representa el valor 999 ¡incorrecto!

5. Para cantidades mayores a 3,000 (MMM) se usan barras horizontales encima


de los sı́mbolos D y M, que indican que el valor se multiplica por 10.
6. Los sı́mbolos romanos siempre se escriben de izquierda a derecha de mayor
valor a menor valor, excepto en los casos en que el sı́mbolo esté restando.
Contamos con un algoritmo muy sencillo para convertir números en notación
decimal a números romanos, que lo que hace es, repetitivamente, traducir un dı́gito
a su representación en romano y multiplicarlo por 10. Lo dejamos para después
de haber visto notación posicional.

El cero y la notación posicional


Un concepto importante es el del número cero. Su origen es muy antiguo. Tam-
bién los mayas tenı́an un sı́mbolo asociado al cero. Este concepto es importante
para poder utilizar notación posicional. La notación posicional es lo que usamos
hoy en dı́a y consiste en que cada sı́mbolo tiene dos valores asociados: el peso y
la posición. Estos valores están dados en términos de lo que conocemos como la
base, que hoy en dı́a corresponde al 10.

EJemplo 1.4.2
El número 327.15 en el sistema decimal (o con base 10) se puede presentar de
la siguiente manera:

327.15  3  102 2  101 7  100 1  101 5  102

Decimos que la notación es posicional porque el valor (o peso) que tiene un


dı́gito depende de la posición que ocupa con respecto al resto de los dı́gitos en un
número y no nada más de cuál dı́gito es –en el sistema romano, el valor del sı́mbolo
9 Introducción

es independiente de la posición que ocupa; lo único que cambia es si suma o resta–.


El sistema que usamos es el decimal posicional, porque es un sistema posicional
que usa al número 10 como base. El peso que se le asigna a cada dı́gito depende
del dı́gito y de su posición. Cada posición lleva un peso de alguna potencia de 10
(decimal) asignadas, alrededor del punto decimal, de la siguiente forma:
... 104 103 102 101 100 101 102 103 . . .
10000 1000 100 10 1 .1 .01 .001
4513.6  4 5 1 3 6
30100  3 0 1 0 0
.075  0 7 5
Hay varias reglas que observamos respecto a la notación posicional:
i. En cada posición se coloca un solo dı́gito.
ii. Los ceros antes del primer dı́gito distinto de cero, desde la izquierda, no apor-
tan nada al número.
iii. Los ceros a la derecha del punto decimal y antes de un dı́gito distinto de cero
sı́ cuentan. No es lo mismo .75 que .00075.
iv. Los ceros a la derecha del último dı́gito distinto de cero después del punto
decimal no cuentan.
v. Los dı́gitos que podemos utilizar son del 0 al 9.
vi. Cada dı́gito aporta su valor especı́fico multiplicado por el peso de la posición
que ocupa.
Sabemos todos trabajar en otras bases para la notación posicional. Si en el
caso del sistema decimal la base es el valor 10 y cada posición representa a una
potencia de 10, para trabajar en otra base cualquiera b, todo lo que tenemos que
hacer es que los dı́gitos vayan del 0 a b  1 y las posiciones sean las correspondientes
a potencias de b.

EJemplo 1.4.3
Para trabajar en base 8 (mejor conocida como octal ) serı́a de la siguiente
manera:
. . . 84 83 82 81 80 81 82 83 ...
4096 512 64 8 1 .125 .015625 .001953125
476.18 4 7 6 1  318.12510
41378 4 1 3 7  214310
1.4 Sistemas numéricos 10

También podemos pensar en base 16 (hexadecimal ), para lo que requerimos de


16 sı́mbolos distintos. Los dı́gitos del 0 al 9 nos proporcionan 10 de ellos. Tenemos
el problema de que con la restricción de que cada posición debe ser ocupada por
un único dı́gito, debemos “inventar” sı́mbolos (o dı́gitos) para seis valores que nos
faltan, que serı́an del 10 al 15 inclusive. La tradición es utilizar las letras A, B, C,
D, E y F (a veces en minúsculas) para los valores consecutivos 10, 11, 12, 13, 14
y 15 respectivamente. Siguiendo la notación posicional, pero en base 16, tenemos:

EJemplo 1.4.4

... 164 163 162 161 160 161 162 ...


65536 4096 256 16 1 .0625 .00390625
476.116 4 7 6 1  1142.062510
BA7C16 11 10 7 12  4774010

Pasamos ahora a la base 2, que es la más importante hoy en dı́a en compu-


tación. Los primeros implementos de cómputo que usaban notación posicional (los
ábacos1 , huesos de Napier, calculadoras) usaban base 10. Mientras las calculadoras
se construı́an con partes fı́sicas (engranes, cuentas) la base 10 no era muy distinta
de cualquier otra base. Pero al intentar construir calculadoras (o computadoras)
electrónicas, la base 10 presentaba problemas de implementación: ¿cómo distin-
guir entre el 4 y el 5, cuando se estaba midiendo en términos de niveles de luz,
lı́quidos, analógicamente? Por lo tanto se optó, aunque no desde el principio, por
usar base 2, que tiene una representación en la electrónica bastante simple. Para
base 2 requerimos de dos sı́mbolos, por lo que utilizamos el 0 y el 1. El 0 se puede
interpretar como ausencia y el 1 como presencia; con esto más allá de cierto nivel
se supone presencia. Esto es mucho más sencillo que ver si tenemos uno de 10 (u 8)
niveles distintos. En la tabla que sigue damos dos ejemplos en notación posicional
en base 2:

1
Los ábacos, en realidad, usaban una especie de base 5. Observa que cada hilo o columna en
un ábaco tiene, en la parte inferior, cuatro cuentas el japonés y cinco el chino, aunque como la
parte superior tiene una cuenta el japonés y dos el chino, se contaba dos veces la parte inferior,
pudiéndose considerar como base 10.
11 Introducción

EJemplo 1.4.5

... 27 26 25 24 23 22 21 20 21 22 23 . . .


128 64 32 16 8 4 2 1 .5 .25 .125
11001101.112 1 1 0 0 1 1 0 1 1 1  205.7510
1010002 1 0 1 0 0 0  4010

Como se puede ver, tratándose de números enteros, es fácil pasar de una base
cualquiera a base 10, simplemente mediante la fórmula

¸
n
num10  d i  bi

i 0

donde di se refiere al dı́gito correspondiente en la i-ésima posición, con la posición


0 en el extremo derecho, mientras que bi se refiere a la base elevada a la potencia
de la posición correspondiente. La n representa a la posición distinta de cero con
la potencia más grande (la primera posición significativa a la izquierda).

¸
5
1010002  d i  2i

i 0
0  2 0 0  21 0  22 1  23 0  24 1  25
 0 0 0 8 0 32
 4010

Para pasar de base 10 a cualquier otra base, se utiliza el algoritmo 1.1. Como
estamos trabajando en notación posicional y debemos ocupar una posición por
cada dı́gito en la base h, trabajaremos con sı́mbolos, por lo que buscamos construir
una cadena de sı́mbolos y no precisamente un número.
El algoritmo construye el número base h pegando dı́gitos de la base h por
la izquierda, trabajando con una cadena. Hay que notar que en la lı́nea 11 se
está concatenando un sı́mbolo a número, por lo que si la base es mayor a 10, se
tendrán que utilizar sı́mbolos para los dı́gitos mayores que 9.
1.4 Sistemas numéricos 12

Algoritmo 1.1 Algoritmo para pasar de base 10 a base h


Entrada: El número entero en base 10: num10
Salida: El número en base h: número
Método: Vamos obteniendo los dı́gitos de derecha a izquierda, empezando
por el menos significativo.
1 /∗ I n i c i a l i z a c i ó n ∗/
2 d i v i d e n d o = num10 ;
3 divisor = h;
4 residuo = 0;
5 número = "" ; // Una c a d e n a v a cı́ a , que no t i e n e
6 // n i n g ú n sı́ m b o l o
7 /∗ Se o b t i e n e dı́ g i t o p o r dı́ g i t o de l a nueva b a s e ∗/
8 repite
9 r e s i d u o = d i v i d e n d o módulo d i v i s o r
10 // ( r e s i d u o e n t e r o de d i v i d e n d o  d i v i s o r ) ;
11 número = p e g a r e l sı́ m b o l o c o r r e s p o n d i e n t e a l r e s i d u o
12 p o r l a i z q u i e r d a a l o que ya l l e v a m o s
13 en número .
14 d i v i d e n d o = d i v i d e n d o  d i v i s o r ; // ( c o c i e n t e e n t e r o )
15 h a s t a que ( d i v i d e n d o = 0 ) ;
16 /∗ En número queda e l r e s u l t a d o ∗/

Veamos un ejemplo de la aplicación de este algoritmo a continuación.

EJemplo 1.4.6
Para pasar de base 10 a base 16 el número 857510 , el algoritmo se ejecutarı́a
de la siguiente manera:

dividendo10 cociente10 residuo10 Sı́mbolo residuo16 Número16


8575 535 15 F F
535 33 7 7 7F
33 2 1 1 17F
2 0 2 2 217F
Para que nos convenzamos que, en efecto 857510  217F16, procedemos a des-
componer 217F en potencias de 16:
217F16  15  160 7  161 1  162 2  163
 15  1 7  16 1  256 2  4096
 15 112 256 8192  857510
13 Introducción

Cuando una de las bases es potencia de la otra, el pasar de una base a la


otra es todavı́a más sencillo. Por ejemplo, si queremos pasar de base 8 a base
2 (binario), observamos que 8  23 . Esto nos indica que cada posición octal se
convertirá exactamente a tres posiciones binarias correspondientes a 22 , 21 y 20
en ese orden. Lo único que tenemos que hacer es pasar cada dı́gito octal presente
en el número a su representación binaria:

75358 7 5 3 5
4 2 1 4 1 2 1 4 1
22 21 20 22 20 21 20 22 20
111 101 011 101 1111010111012

Algo similar se hace para pasar de base 16 a base 2, aunque tomando para cada
dı́gito base 16 cuatro dı́gitos base 2.

7A35C16 7 A 3 5 C
4 2 1 8 2 2 1 4 1 8 4
22 21 20 23 21 21 20 22 20 23 22
0111 1010 0011 0101 1100 11110100011010111002

Hay que tener cuidado de no eliminar los ceros a la izquierda en los dı́gitos inter-
medios, sino únicamente en el primer dı́gito octal o hexadecimal a la izquierda.

El proceso inverso, para pasar de base 2 por ejemplo a base 16, como 16  24
deberemos tomar 4 dı́gitos binarios por cada dı́gito hexadecimal (23 , 22 , 21 y 20
respectivamente), separándolos desde la derecha y rellenando con ceros al primer
grupo de la izquierda:

101111010111012 00102 11112 01012 11012


10 1111 0101 1101 21 23 22 21 2 0
2 2 0
2 2 3
22 20
210 1510 510 1310
216 F16 516 D16 2F 5D

Las computadoras actuales son, en su inmensa mayorı́a, digitales, esto es, que
representan su información de manera discreta, con dı́gitos. Operan en base 2
(binario) ya que la electrónica es más sencilla en estos términos. Sin embargo,
hay procesos que no son discretos, como las ondas de luz o sonoras. Pero hoy en
dı́a se pueden alcanzar excelentes aproximaciones de procesos continuos mediante
dı́gitos binarios. Para ello se cuenta con componentes analógicos/digitales que
1.5 La arquitectura de von Neumann 14

transforman señales analógicas (continuas) en señales digitales (discretas). Hubo


una época en que se tenı́a mucha fe en las computadoras analógicas, aquellas que
funcionaban con dispositivos continuos, pero prácticamente han desaparecido del
mercado, excepto por algunas de propósito muy especı́fico, o las que convierten
señales analógicas en señales digitales, o viceversa.
La siguiente pregunta que debemos hacernos es:
¿Cuáles son los distintos elementos que requerimos para poder implementar un
algoritmo en una computadora digital?
¿Cómo se representan en binario esos distintos elementos?
Pensemos por ejemplo en las máquinas de escribir. La orden para que se es-
criba una letra determinada se lleva a cabo oprimiendo una cierta tecla. Esto es
porque hay una conexión mecánica (fı́sica) entre la tecla del teclado y el dado que
imprime la tecla. La primera computadora, la ENIAC, funcionaba de manera muy
similar. Cada vez que se deseaba que resolviera algún problema, se alambraban
los paneles de la computadora para que hubiera conexiones fı́sicas entre lo que
se recibı́a en el teletipo y las operaciones que se ejecutaban. De manera similar,
las calculadoras mecánicas, al darle vuelta a una manivela, se conseguı́a que los
engranes seleccionados efectuaran una determinada operación.
Las computadoras modernas, de propósito general, vienen alambradas para
reconocer ciertos patrones, de forma similar a como lo hacı́a el telar de Jackard o
la máquina del censo de Hollerith. Cada patrón indica una operación a realizar-
se. Los patrones son números binarios con un número fijo de posiciones (binary
digits). A cada conjunto de posiciones de un cierto tamaño se le llama una pala-
bra. El tamaño de palabra es, en general, un número de bits que corresponde a
una potencia de 2: 8, 16, 32, 64, 128. A los grupos de 8 bits se les llama byte. Al
conjunto de patrones distintos es a lo que se conoce como lenguaje de máquina,
que por supuesto es “personal” de cada modelo o tipo de computadora. Original-
mente se distinguı́a entre micro, mini y computadoras por el tamaño en bits de
sus palabras. El tamaño de la palabra va unido al poderı́o de la máquina: si tengo
más bits por palabra tengo más patrones posibles, y por lo tanto un lenguaje de
máquina más extenso y posibilidad de representar números más grandes o con más
precisión2 . Las primeras microcomputadores tenı́an palabras de 8 bits, mientras
que las “grandes” computadoras tenı́an palabras de 64 bits. Las supercomputado-
ras, que surgieron alrededor de 1985, tenı́an palabras de 128 bits y posibilidades
de proceso en paralelo.
El tamaño de la palabra también le da velocidad a una computadora, pues
indica el número de bits que participan electrónicamente en cada operación.
2
En breve veremos la representación de datos en la computadora.
15 Introducción

1.5 La arquitectura de von Neumann

Se le llama arquitectura de una computadora a la organización que tiene en sus


componentes electrónicos, y la manera como éstos están integrados para funcionar.
Lo que se conoce como arquitectura de von Neumann es una organización
muy parecida a la de Babbage: tenemos un procesador central –el molino de
Babbage– en el que se ejecutan las operaciones aritméticas y de comparación
(lógicas); una memoria central que se utiliza para almacenar datos, resultados
intermedios y el programa a ejecutarse; tenemos unidades de entrada y salida
(input/output) que sirven para darle a la computadora el programa y los datos
y recibir los resultados; por último, tenemos memoria externa o auxiliar, como
discos, diskettes, cintas magnéticas, que nos sirven para almacenar, ya sean datos
o programas, de una ejecución a otra, sin tener que volver a realizar el proceso, o
sin que tengamos que volverlos a proporcionar. Un esquema de una computadora
con arquitectura de von Neumann se muestra en la figura 1.1.

Figura 1.1 Arquitectura de von Neumann

Memoria Memoria Central


auxiliar
Discos

Procesador
central
Unidad de control
Unidad aritmética
Unidad lógica
Cache

Dispositivos de entrada Dispositivos de salida


Teclado, scanner, usb Monitor, impresora, usb,
bocinas

Las flechas que van de un componente a otro pueden tener distintas formas
de funcionar y muy diversas capacidades. Una de las formas en que funciona es
1.5 La arquitectura de von Neumann 16

lo que se conoce como un bus. Por ejemplo, si la capacidad de la lı́nea que va de


memoria al procesador es de menos de una palabra, aunque la computadora tenga
palabras muy grandes, la velocidad de la máquina se va a ver afectada.
La memoria está “cuadriculada” o dividida en celdas. Cada celda ocupa una
posición dentro de la memoria, aunque en principio cada una es igual a cualquier
otra: tiene el mismo número de bits y las mismas conexiones. Se puede ver como un
vector de celdas, cuyo primer elemento tiene el ı́ndice cero. Se habla de posiciones
“altas” y posiciones “bajas” refiriéndose a aquéllas que tienen ı́ndices grandes o
pequeños respectivamente. En cada celda de memoria se puede colocar (escribir,
copiar) una instrucción, un número, una cadena de caracteres, etc.
El proceso mediante el que se ejecuta un programa en lenguaje de máquina ya
colocado en la memoria de la computadora es el siguiente:

i. La unidad de control se coloca frente a la primera instrucción de lenguaje de


máquina una vez cargado el programa.

ii. La unidad de control entra a un ciclo conocido como fetch/execute:

(fetch) Carga a la unidad de control la siguiente instrucción a ser ejecutada.


(execute) La Unidad de Control se encarga de ejecutar la instrucción, va-
liéndose para ello de cualquiera de los componentes de la máquina.

El tipo de instrucciones que tiene una computadora incluyen instrucciones pa-


ra sumar, restar, multiplicar, dividir, copiar, borrar, recorrer el patrón de bits,
comparar y decidir si un número es mayor que otro, etc. En realidad son instruc-
ciones que hacen muy pocas cosas y relativamente sencillas. Recuérdese que se
hace todo en sistema binario.

Lenguajes de programación

Un lenguaje de programación es aquel que nos permite expresar un problema


de tal manera que podamos instalarlo (cargarlo) en la computadora y se ejecute.
Hasta ahora sólo hemos visto el lenguaje de máquina, y éste era el único disponible
con las primeras computadoras de propósito general.
Programar en binario es, en el mejor de los casos, sumamente tedioso y com-
plicado. El programador (que es quien escribe los programas) tiene que tener un
17 Introducción

conocimiento muy profundo de la computadora para la que está programando.


Además de eso, tiene que ejercer un cuidado extremo para no escribir ningún 1
por 0, o para no equivocarse de dirección. Lo primero que se hizo fue escribir en
octal, ya que era un poco más claro que binario. El siguiente paso fue asociar
nemónicos a las instrucciones, asociando a cada patrón de bits un “nombre” o
identificador: add, sub, mul, div, etc. A esto se le llamó lenguaje ensamblador. Se
construyó un programa, llamado ensamblador, que se encargaba de traducir los
nemónicos de este estilo y las direcciones escritas en octal a binario. Este programa
no es algo complicado de hacer. Tanto el programa ensamblador como el progra-
ma a traducir se alimentaban, cargados en tarjetas perforadas – ver figura 1.2. El
primer paquete era el programa ensamblador, escrito en binario; a continuación
se presentaba el programa que se deseaba traducir, como datos del primero. La
computadora contaba con un tablero, en el que se le indicaba que empezara a
cargar el programa ensamblador, y una vez cargado (1), empieza a ejecutarlo. El
programa ensamblador indicaba que tenı́a que traducir las siguientes tarjetas (o
cinta perforada), conforme las fuera leyendo (2), y producir tarjetas con el pro-
grama en binario (3) o, más adelante, cargar el programa binario a memoria para
ser ejecutado (4); al ejecutarse el programa en binario, se leı́an los datos (5) y se
producı́a el resultado (6).

Figura 1.2 Proceso para ejecutar un programa escrito en ensamblador

(4)
Programa
objeto
(binario)
(3)
Unidad
Datos para de control
el programa (5)
Programa a
ejecutar Resultados
Programa
(2) MEMORIA (6)
ensamblador
(binario) (1)

El siguiente paso en lenguajes de programación fue el de los macroensamblado-


res, que asignaban etiquetas a las posiciones de memoria que se estaban utilizando
1.5 La arquitectura de von Neumann 18

para acomodar los datos y resultados intermedios, y las posiciones donde iba a
quedar el código. El proceso de traducción era sencillo, ya que se agregaban a
las tablas de traducción del código los equivalentes en octal. También se permitı́a
construir secuencias pequeñas de código, a las que nuevamente se les asociaba un
nombre o identificador, y que podı́an presentar parámetros. A estas secuencias se
les llamaba macros y de ahı́ el nombre de macroensamblador.
Las computadoras se utilizaban tanto con fines cientı́ficos como comerciales.
En el uso cientı́fico era muy común expresar fórmulas matemáticas, que tenı́an que
“despedazarse” en operaciones básicas para poderse llevar a cabo – ver figura 1.3.

Figura 1.3 Codificación en ensamblador de fórmulas matemáticas

Fórmula Programa simplificado

?2 def x1 100 mul b2 b b

 b  4ac
b def a 102 mul ac a c
x1 def b 104 mul ac 4 ac
2a
def c 106 mul a2 2 a
def ac 108 sub rad ac b2
def a2 110 sqrt rad rad
def b2 112 sub x1 rad b
def rad 114 div x1 x1 a2

El siguiente paso importante fue el de permitirle a un programador que espe-


cificara su fórmula de manera similar a como se muestra en la parte izquierda de
la figura 1.3 –x1  pb raı́z pb2  4  a  cqq{p2  aq–. El primer lenguaje de uso
generalizado orientado a esto fue FORTRAN –For mula Translator–, alrededor de
1956. Pocos años después se desarrolló un lenguaje para usos comerciales, donde
lo que se deseaba es poder manejar datos a distintos niveles de agregación. Es-
te lenguaje se llamaba COBOL –COmon B ussiness Oriented Language–. Ambos
lenguajes, o versiones modernizadas, sobreviven hasta nuestros dı́as.
Estos lenguajes tenı́an un formato también más o menos estricto, en el sentido
de que las columnas de las tarjetas perforadas estaban perfectamente asignadas,
y cada elemento del lenguaje tenı́a una posición, como en lenguaje ensamblador.
El proceso por el que tiene que pasar un programa en alguno de estos lenguajes
de programación para ser ejecutado, es muy similar al de un programa escrito
en lenguaje ensamblador, donde cada enunciado en lenguaje de “alto nivel” se
traduce a varios enunciados en lenguaje de máquina (por eso el calificativo de
19 Introducción

“alto nivel”, alto nivel de información por enunciado), que es el único que puede
ser ejecutado por la computadora.
Hacia finales de los años 50 se diseñó el lenguaje, ALGOL –ALGorithmic
Oriented Language– que resultó ser el modelo de desarrollo de prácticamente to-
dos los lenguajes orientados a algoritmos de hoy en dı́a, como Pascal, C, C++,
Java y muchos más. No se pretende ser exhaustivo en la lista de lenguajes, bas-
te mencionar que también en los años 50 surgió el lenguaje LISP, orientado a
inteligencia artificial; en los años 60 surgió BASIC, orientado a hacer más fácil
el acercamiento a las computadoras de personas no forzosamente con anteceden-
tes cientı́ficos. En los años 60 surgió el primer lenguaje que se puede considerar
orientado a objetos, SIMULA, que era una extensión de ALGOL. En los aproxi-
madamente 50 años que tienen en uso las computadoras, se han diseñado y usado
miles de lenguajes de programación, por lo que pretender mencionar siquiera a los
más importantes es una tarea titánica y no el objetivo de estas notas.

Representación de la información

Acabamos de ver que la representación de los programas debe ser eventual-


mente en lenguaje de máquina, o sea en binario. También tenemos restricciones
similares para el resto de la información, como son los datos, los resultados inter-
medios y los resultados finales. Al igual que con los lenguajes de programación,
si bien la computadora sólo tiene la posibilidad de representar enteros positivos
en binario, debemos encontrar la manera de poder representar letras, números de
varios tipos, como enteros negativos, reales, racionales, etc. Para ello se sigue una
lógica similar a la del lenguaje de máquina.

Caracteres

Supongamos que tenemos un tamaño de palabra de 16 bits y queremos repre-


sentar letras o caracteres. Simplemente hacemos lo que hacı́amos en la primaria
cuando querı́amos mandar “mensajes secretos”: nos ponemos de acuerdo en algún
código.
El primer código que se utilizó fue el BCD, que utilizaba 6 bits por carácter.
Con esto se podı́an representar 64 caracteres distintos (en 6 bits hay 64 posibles
1.5 La arquitectura de von Neumann 20

enteros, del 0 al 63). Con este código alcanzaba para las mayúsculas, los dı́gitos y
algunos caracteres importantes como los signos de operación y de puntuación.
Con el perfeccionamiento de las computadoras se requirieron cada vez más
caracteres, por lo que se extendió el código a 7 y 8 bits, con el código ASCII, que
se usó mucho para transmitir información, y el código EBCDIC que se usó como
código nativo de muchas computadoras, respectivamente. El lenguaje Java utiliza
Unicode, que ocupa 16 bits, para representar a cada carácter. Con esto tiene la
posibilidad de utilizar casi cualquier conjunto de caracteres de muchı́simos de los
idiomas en uso actualmente.
Se requiere de programas que transformen a un carácter en su código de máqui-
na y viceversa. Estos son programas sencillos que simplemente observan “patro-
nes” de bits y los interpretan, o bien observan caracteres y mediante una tabla
los convierten al patrón de bits en el código que utilice la computadora.
Prácticamente todo manual de programación trae una tabla de los distintos
códigos que corresponden a los caracteres. Estas tablas vienen con varias columnas;
en la primera de ellas vendrá el carácter y en columnas subsecuentes su código en
octal, hexadecimal, binario, utilizando alguno de estos esquemas para dar el código
que le corresponde en ASCII, EBCDIC o Unicode. Por supuesto que requerimos
de 32,768 para mostrar la codificación de Unicode, por lo que no lo haremos, más
que en la medida en que tengamos que conocer el de algunos caracteres especı́ficos.

Números enteros

Ya vimos la manera en que se representan números enteros en la computadora:


simplemente tomamos una palabra y usamos notación posicional binaria para ver
el valor de un entero.
Hoy en dı́a las computadoras vienen, en su gran mayorı́a, con palabras de al
menos 32 bits. Eso quiere decir que podemos representar enteros positivos que
van desde el 0 (32 bits apagados) hasta 232  1 (todos los bits prendidos). Pero,
¿cómo le hacemos para representar enteros negativos? Tenemos dos opciones. La
primera de ellas es la más intuitiva: utilizamos un bit, el de la extrema izquierda,
como signo. A esta notación se le llama de signo y magnitud. Si éste es el caso,
tenemos ahora 31 bits para la magnitud y 1 bit para el signo –ver figura 1.4 con
palabras de 16 bits–.
La representación de signo y magnitud es muy costosa. Por ejemplo, cuando
se suman dos cantidades que tienen signos opuestos, hay que ver cuál es la que
tiene mayor magnitud, pues la suma se puede convertir en resta de magnitudes.
Veamos el algoritmo 1.2 en la página opuesta.
21 Introducción

Figura 1.4 Enteros en signo y magnitud

s
ihkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkikkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkj
magnitud
g
n
o 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20
0 0 0 0 1 0 1 1 0 1 1 1 0 0 1 1 2931
1 0 0 0 1 0 1 1 0 1 1 1 0 0 1 1 2931

Como se puede ver, los circuitos que se requieren para sumar dos números en
notación de signo y magnitud son muy complicados, y por lo tanto muy caros.

Algoritmo 1.2 Suma de dos números enteros representados con signo y magnitud
1 Sean a y b l o s e n t e r o s a sumar
2 Sa  s i g n o de a ; Ma  magnitud de a ;
3 Sb  s i g n o de b ; Mb  magnitud de b ;
4 i f ( Sa  Sb ) then
5 Ssuma  Sa ;
6 Msuma  Ma Mb ;
7 else
8 i f ( Ma ¡ Mb ) then
9 Ssuma  Sa ;
10 Msuma  Ma  Mb ;
11 else
12 Ssuma  Sb ;
13 Msuma  Mb  Ma ;

El otro modo de codificar enteros positivos y negativos es lo que se conoce


como complemento a 2. En este método, al igual que con signo y magnitud, se
parte al dominio en 2 partes, los que tienen al bit de potencia más alta en 0, y los
que lo tienen en 1; los que tienen el bit más alto en cero son los enteros positivos,
y los que lo tienen en 1 son los enteros negativos. Para saber la magnitud del
número, en el caso de los positivos se calcula igual que con signo y magnitud, pero
en el caso de los negativos, la magnitud es la que resulta de restar la palabra de
una con una posición más, y donde todas las posiciones originales de la palabra
tienen 0, con un 1 en la posición extra. Veamos algunos ejemplos en la figura 1.5
en la siguiente página.
1.5 La arquitectura de von Neumann 22

Figura 1.5 Números enteros en complemento a 2

215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20


Un entero positivo en complemento a 2:
0 0 0 0 1 1 1 0 0 0 0 0 1 0 0 0 3, 592 p1 q
La palabra con el complemento a 2: 216 
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 65, 536 p2 q
Un entero negativo en complemento a 2:
1 0 0 0 1 1 1 0 0 0 0 0 1 0 0 0 36, 360 p3 q
La magnitud del entero original:
0 0 1 1 1 0 0 0 1 1 1 1 1 1 0 0 0 29, 176 p4 q

Como vemos en la figura 1.5, el bit 15 (el que corresponde a 215 ) también
nos indica de cierta forma, como en la notación de signo y magnitud, cuando
tenemos un entero negativo. En el caso de 16 bits, los enteros positivos son del 0
al 215  1  32, 767 que corresponde a una palabra de 16 bits con todos menos el
bit 15 prendidos:

215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20


0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 32, 767

A partir del número 215  32, 768 y hasta 216  1  65, 535, que corresponde
a todos los bits en una palabra de 16 bits prendidos, estamos representando a
números negativos –ver en la figura 1.5, lı́nea (3)–. En los enteros negativos el bit
15 está siempre prendido, por lo que reconocemos el signo del número.
La magnitud (o el valor absoluto) del número que estamos viendo se obtiene
sacando el complemento a 2 de la palabra en cuestión. El complemento a 2 se
obtiene de tres maneras posibles:
i. Se resta en binario de un número de 17 bits –en la figura 1.5, lı́nea (2)– con
ceros en todos los bits, menos el bit 16, que tiene 1.

10000000000000000 65536
 1000111000001000 36360
0111000111111000 29176
23 Introducción

ii. Se complementan cada uno de los bits de la palabra de 16 bits (se cambian
los 1’s por 0’s y los 0’s por 1’s) y después se le suma 1 a lo que se obtuvo.

1000111000001000 Número original


0111000111110111 Número complementado a 1
1 Se le suma 1
0111000111111000  29176
iii. Se procesan los bits de derecha a izquierda: se dejan igual los dı́gitos hasta e
incluyendo el primer 1; a partir de ese punto se complementan todos los bits
(convertirá al bit de signo en 0).

1000111000001000 Número original


Se dejan igual hasta el primer 1 y se complementan
0111000111111000  29176
Cabe aclarar que nos interesa sacar el complemento a 2 sólo en el caso de los
enteros negativos –los que tienen el bit más alto prendido– pues en el caso de los
enteros positivos su magnitud está dada directamente en binario.
La gran ventaja de la notación en complemento a 2 es que es sumamente fácil
hacer operaciones aritméticas como la suma y la resta. En complemento a 2, todo lo
que tenemos que hacer es sumar (o restar) utilizando los 16 bits. Pudiera suceder,
sin embargo, que el resultado no sea válido. Por ejemplo, si sumamos dos números
positivos y el resultado es mayor que la capacidad, tendremos acarreo sobre el
bit 15, dando aparentemente un número negativo. Sabemos que el resultado es
inválido porque si en los dos sumandos el bit 15 estaba apagado, tiene que estar
apagado en el resultado. Algo similar sucede si se suman dos números negativos
y el resultado “ya no cabe” en 16 bits –ver figura 1.6–.

Figura 1.6 Suma de dos números enteros con complemento a 2

215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20


0 0 0 0 0 0 0 0 1 1 0 0 1 1 1 1 207
+ 0 0 0 1 0 0 0 1 1 1 0 0 0 0 1 1 4547
0 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 4754

0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 25
+ 1 1 1 1 1 1 0 0 0 1 0 1 0 1 0 1 939
1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 0 914
1.5 La arquitectura de von Neumann 24

Pueden verificar, sumando las potencias de 2 donde hay un 1, que las sumas
en la figura 1.6 se hicieron directamente y que el resultado es el correcto.
La desventaja del complemento a 2 es que se pueden presentar errores sin que
nos demos cuenta de ello. Por ejemplo, si le sumamos 1 al máximo entero positivo
(una palabra con 0 en el bit 15 y 1 en el resto de los bits) el resultado resulta ser
un número negativo, aquel que tiene 1 en todas las posiciones de la palabra–ver
figura 1.7.

Figura 1.7 Sumando 1 al máximo entero positivo

215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20


0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 32767
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32768

En algunos lenguajes de programación este tipo de errores se detectan en


ejecución, pero en la mayorı́a no. Hay que tener presente que la representación
interna es con complemento a 2, para manejar de manera adecuada este tipo de
posibles errores.
Muchas veces el tamaño de la palabra de una computadora no es suficiente
para los números que deseamos representar. Entonces, el lenguaje de programación
puede usar más de una palabra para representar enteros, simplemente utilizando
notación posicional base 216 , de manera similar a como se maneja la base 2. Se
dice entonces que la aritmética se hace por software.

Números reales

Para representar a los número reales tenemos dos opciones, conocidas como
punto fijo y punto flotante.

Punto fijo: Como su nombre l;o indica, la representación en bits es la de un


entero (posiblemente en complemento a 2) al que se le aplica un factor de
escalamiento constante. Por ejemplo, pensando en base 10 y si tenemos un
factor de escala de 1{10000 (que corresponde as 4 posiciones decimales), el
número 325.15 estarı́a representado como 3251500, que al multiplicarlo por
1{10000 nos darı́a precisamente 325.15. El número 1.2575 estarı́a represen-
tado como 12575. En el caso de notación binaria el factor de escala es una
25 Introducción

potencia negativa de 2 (1{2 para una posición, 1{4 para dos posiciones y
ası́ sucesivamente).–ver figura 1.8–.
Como se puede ver en esta figura, se mantiene la notación de complemento
a 2, con el bit más alto indicándonos que el número es negativo.

Figura 1.8 Notación con punto fijo

215 214 213 212 211 210 29 1 28 27 26 25 24 23 22 21 20


0 1 0 0 1 1 0 1 0 1 0 0 0 0 1 1 19779{26  309.047
1 0 0 1 1 0 1 1 0 1 0 1 0 1 1 0 99560{26  1555.625

Una de las ventajas de este tipo de notación es que es muy sencillo hacer
operaciones aritméticas, pues se usa a toda la palabra como si fuera un entero
y el proceso de colocar el punto decimal se hace al final. Sin embargo, tiene
una gran desventaja que es la poca flexibilidad para representar números
que tengan muchos o muy pocos dı́gitos significativos en la fracción.

Punto flotante: El punto flotante es otra manera de representar números reales.


Básicamente se logra esta notación dividiendo a la palabra en dos partes,
una para la mantisa y la otra para el exponente, utilizando lo que se conoce
como notación cientı́fica. Veamos los siguientes ejemplos:

1.32456  106 1324560


1.32456  106 .00000132456
1.32456  103   1324.56
Como podemos ver del ejemplo anterior, nos ponemos de acuerdo en cuántos
dı́gitos van a estar a la izquierda del punto decimal, representando a todos
los números reales con ese número de enteros. A continuación damos una
potencia de 10 por la que hay que multiplicar el número para obtener el
número que deseamos.
Una abreviatura de esta notación consiste en escribir los dos números ante-
riores de la siguiente forma:

1.32456E6 1324560
1.32456E  6 .00000132456
1.32456E3   1324.56
1.5 La arquitectura de von Neumann 26

Esta representación es muchı́simo más versátil que la de punto fijo, y de


hecho es la que se usa generalmente, aunque la representación de los núme-
ros es en binario. Si damos mucho espacio para los exponentes tenemos la
posibilidad de representar números muy grandes o muy pequeños (con el
exponente negativo). Si en cambio le damos mucho espacio a la mantisa,
vamos a tener números con mucha precisión (muchas cifras significativas).
Es conveniente encontrar un balance entre la magnitud y la precisión. Por
ejemplo, la IEEE tiene sus estándares.
Las operaciones con este tipo de números son un poco más complejas que con
punto fijo. Por ejemplo, si deseamos sumar dos números tenemos primero
que llevarlos a que tengan el mismo exponente, y una vez hecho esto se puede
llevar a cabo la suma. En cambio, multiplicar dos números es sumamente
fácil. ¿Por qué?

Al igual que en los números enteros, además de lo que nos proporcione el


hardware de la computadora como tamaño de palabra, por software se pueden
usar tantas palabras como uno quiera para representar a un entero o a un real.
Cada lenguaje de programación proporciona un conjunto de enteros y reales de
diversos “tamaños”.
La inmensa mayorı́a de las computadoras utilizan complemento a 2 y repre-
sentación de punto flotante para representar números.

Limitaciones en la representación interna

Vimos ya que en la computadora no podemos representar a todos y cualquier


entero: tenemos un número finito de enteros distintos que podemos representar,
no importa que tan grande sea la palabra de una computadora dada, ya que
tenemos un número finito de combinaciones de posiciones con 0 y posiciones con
1 en cualquier tamaño dado de palabra.
Algo similar ocurre con los números reales. No sólo no tenemos la posibilidad
de representar a un número infinito de números reales, sino que además tampoco
tenemos la posibilidad de representar la densidad de los números reales. Como
estamos usando binario para representar a números que manejamos –y pensamo-
s– como números en base 10, habrá números que no tengan un representación
exacta al convertirlos a base 2 (por supuesto que estamos hablando de núme-
ros fraccionarios). Adicionalmente, al agregarle 1 a una mantisa, no obtenemos
el “siguiente” número real, ya que estamos sumando, posiblemente, en la parte
fraccionaria. Por ello no es posible tener una representación para todos y cada uno
27 Introducción

de los números reales en un intervalo dado: nuevamente, la cantidad de números


reales en un intervalo dado es un número infinito (en realidad, más grande que
infinito, ℵ0 ) y lo que tenemos es un número finito de combinaciones.

1.6 Ejecución de programas


Mencionamos que lo único que puede ejecutar una computadora, directamente
en su hardware, es un programa escrito en su lenguaje de máquina. Por lo que
para que nuestros programas escritos en Java puedan ser ejecutados, deberán ser
transformados a lenguaje de máquina.
¿Recuerdan lo que hacı́amos para obtener un programa escrito en lenguaje de
máquina a partir de uno escrito en ensamblador? Se lo dábamos como datos a un
programa que traducı́a de enunciados en ensamblador a enunciados en lenguaje
de máquina. Algo similar hacemos cuando estamos trabajando en un lenguaje de
alto nivel.
En general, tenemos dos maneras de conseguir ejecutar un programa escrito
en un lenguaje de alto nivel. La primera de ellas es mediante un intérprete y la
segunda mediante un compilador. Veamos qué queremos decir con cada uno de
estos términos.
Definición 1.7 Un intérprete es un programa que una vez cargado en la memoria de una
computadora, y al ejecutarse, procede como sigue:
Toma un enunciado del programa en lenguaje de alto nivel, llamado el código
fuente.
Traduce ese enunciado y lo ejecuta.
Repite estas dos acciones hasta que alguna instrucción le indique que pare,
o bien tenga un error fatal en la ejecución.

Definición 1.8 Un compilador es un programa que una vez que reside en memoria y
al ejecutarse, toma un programa fuente y lo traduce completo a un programa
equivalente en otro lenguaje de programación, que generalmente es lenguaje de
máquina.
Mientras que un intérprete va traduciendo y ejecutando, el compilador no
se encarga de ejecutar, sino simplemente de producir un programa equivalente,
susceptible de ser cargado a la memoria de la máquina y ser ejecutado en un
momento posterior.
A los intérpretes se les conoce también como máquinas virtuales, porque una
1.6 Ejecución de programas 28

vez que están cargados en una máquina se comportan como si fueran otra compu-
tadora, aquella cuyo lenguaje de máquina es el que se está traduciendo y ejecu-
tando.

1.6.1. Filosofı́as de programación

Dependiendo del tipo de problema que queramos resolver –numérico, admi-


nistrativo, de propósito general, inteligencia artificial, lógico– tenemos distintos
lenguajes de programación que permiten representar de mejor manera los forma-
tos de los datos y los recursos que requerimos para resolver el problema. Ası́,
para procesos numéricos se requieren bibliotecas muy extensas con funciones ma-
temáticas, un manejo sencillo de matrices y, en general, de espacios de varias
dimensiones, etc. El lenguaje que fue diseñado para este tipo de problemas fue
FORTRAN y recientemente se usa C. Si lo que queremos es resolver problemas
administrativos tenemos a COBOL, que se rehúsa a morir, o VisualBasic que
provee una fabricación rápida de interfaces con el usuario3 . Como representantes
de lenguajes de propósito general tenemos Pascal, C, Algol. Para problemas que
involucran cambios de estado en los datos, o situaciones que suceden no forzosa-
mente una después de la otra se cuenta con lenguajes orientados a objetos como
C++, SmallTalk y Java. Para resolver problemas que involucran manejo simbólico
de datos, como lo que se requiere para Inteligencia Artificial, se tienen lenguajes
como LISP y Scheme. Para problemas de tipo lógico se tiene ProLog. Tenemos
también lenguajes que se utilizan para trabajar en el Web y que se conocen co-
mo de desarrollo acelerado, como Python y Ruby. En fin, casi cualquier tipo de
aplicación que se nos ocurra, se puede diseñar un lenguaje de programación para
el cuál el lenguaje que se utilice en el algoritmo sea muy cercano al lenguaje de
programación: éste es el objetivo que se persigue cuando se diseñan nuevos lengua-
jes de programación. Este curso se enfocará a resolver problemas que se expresan
fácilmente con orientación a objetos, y el lenguaje que utilizaremos es Java.
Es importante darse cuenta que cualquier problema se puede resolver utilizan-
do cualquier lenguaje: finalmente, todo programa tiene que traducirse a lenguaje
de máquina, por lo que no importa en qué lenguaje hayamos programado, termi-
naremos con un programa equivalente escrito en lenguaje de máquina. El meollo
del asunto es, simplemente, qué tanto trabajo nos cuesta pensar en el problema
de un cierto tipo cuando lo queremos resolver en un lenguaje pensado para resol-
ver otro tipo distinto de problemas. Buscamos que el lenguaje de programación
3
Una interfaz con el usuario es aquel programa que permite una comunicación mejor entre
el usuario y el programa en la computadora. Se usa para referirse a las interfaces gráficas.
29 Introducción

se ajuste de manera sencilla a nuestro modo de pensar respecto al problema que


deseamos resolver.

1.7 Caracterı́sticas de Java

Java es un lenguaje orientado a objetos, cuyo principal objetivo de diseño es su


portabilidad, esto es que sin cambios sed pueda ejecutar en cualquier computadora.
Otro objetivo de Java fue su uso remoto en la red. Una manera de hacer programas
escritos en Java es mediante el siguiente truco:

Se traduce el programa escrito en Java a un lenguaje de bajo nivel, tipo


lenguaje de máquina, pero que no sea el de una máquina en especı́fico.
Se construye (programa) un intérprete de este “lenguaje de máquina”, co-
nocido como una máquina virtual, y se ejecuta el programa en lenguaje de
máquina en la máquina virtual de Java.

Esto resulta relativamente sencillo. El “lenguaje de máquina” de Java se llama


bytecode. Es más fácil construir una máquina virtual que entienda el bytecode que
construir un compilador para cada posible lenguaje de máquina. Además, una vez
que está definida la máquina virtual, se le pueden agregar capacidades al lenguaje
simplemente dando su transformación a bytecode.
Por todo esto, para ejecutar un programa escrito en Java necesitamos:

a. Tener un compilador de Java que traduzca de programas escritos en Java a


bytecode (javac).
b. Tener un intérprete de bytecode (o máquina virtual de Java) a la que se le da
como datos el programa en bytecode y los datos pertinentes al programa.

Ejercicios

1.1.- De entre los conceptos vistos, cuál es el que consideras más relevante para:
(a) La construcción de la computadora digital.
(b) El diseño e implementación de lenguajes de programación.
1. Ejercicios 30

(c) La automatización de procesos que manejan información.

1.2.- En las siguientes dos columnas, relaciona una columna con otra. Cada enti-
dad en la primer columna puede estar relacionada con más de una entidad
en la segunda columna y viceversa.
entrada
ciencias de la computación
definición
tecnologı́as de la información
ejecución
informática
sistema binario
algoritmo
automatización
Hollerith
logaritmos
Shannon
álgebra
Turing
binario
Gödel
octal
Pascal
programa almacenado
Mauchly
eficiencia
von Neumann
registro unitario

1.3.- ¿Cuál o cuáles son las diferencias entre un algoritmo y un procedimiento?

1.4.- ¿Cuál de las caracterı́sticas de un algoritmo consideras más importante para


poder ejecutarlo? Justifica.

1.5.- ¿Cuáles son las ventajas y desventajas de un sistema binario frente a un


sistema numérico decimal?

1.6.- Transforma de base 10 a base 7 las siguientes cantidades:


(a) 439210 (c) 33610
(b) 342710 (d) 25710
1.7.- Transforma de base 8 a base 10 las siguientes cantidades:
(a) 56318 (c) 43748
(b) 3578 (d) 76548
1.8.- Transforma las siguientes cantidades de base 10 a base 16:
(a) 286110 (c) 36310
(b) 3445710 (d) 275210
1.9.- Transforma las siguientes cantidades de base 16 a base 10:
(a) 4A5C16 (c) 3A16
(b) 499916 (d) F F F16
31 Introducción

1.10.- Pasa de hexadecimal a binario las siguientes cantidades:


(a) A35C16 (c) 33316
(b) 49F 916 (d) F F F16
1.11.- Pasa de hexadecimal a octal las siguientes cantidades:
(a) A4C516 (c) 3F 316
(b) 944416 (d) 88816
1.12.- ¿Qué será más tardado de ejecutar, un programa compilado o uno inter-
pretado?

1.13.- Supongamos que tenemos una representación de enteros de signo y magni-


tud que ocupa 16 bits.

(a) ¿Cuál es el menor número entero que se puede representar?


(b) ¿Cuál es el mayor número entero que se puede representar?

1.14.- Supongamos que tenemos una representación de enteros de complemento a


2 que ocupa 16 bits.

(a) ¿Cuál es el menor número entero que se puede representar?


(b) ¿Cuál es el mayor número entero que se puede representar?

1.15.- Supongamos que tenemos números reales de punto flotante con un expo-
nente en complemento a 2 con 8 bits y una mantisa en complemento a 2 con
24 bits.

(a) ¿Cuál es la precisión, en decimal, que tienen los reales con esta repre-
sentación?
(b) ¿Cuál es la máxima magnitud (en decimal) de un real con esta repre-
sentación?
(c) ¿Cuántos posibles números reales distintos podemos representar de esta
manera?

1.16.- ¿Es la arquitectura de von Neumann relevante para las computadoras mul-
ticore?
El proceso del
software 2
2.1 ¿Qué es la programación?

Como ya platicamos al hablar de lenguajes de programación, la programa-


ción consiste en elaborar un algoritmo, escrito en un lenguaje susceptible de ser
ejecutado por una computadora, para resolver una clase de problemas.
Podemos pensar en un algoritmo como la definición de una función. Una vez
definida ésta, se procede a aplicar la función a distintos conjuntos de argumentos
(datos) para obtener el resultado.
El proceso que nos lleva finalmente a aplicar un programa a datos determinados
conlleva varias etapas, algunas de ellas repetitivas. En el estado inicial de este
proceso tendremos un enunciado del problema que deseamos resolver, junto con
la forma que van a tomar los datos (cuántos datos, de qué tipo). En el estado
final deberemos contar con un programa correcto que se puede instalar en una
computadora para ser ejecutado con cualquier conjunto de datos que cumpla las
especificaciones planteadas. Por ello deberemos observar los siguientes pasos (ver
figura 2.1 en la siguiente página para el proceso del Software1 ):
1
El diagrama que presentamos es lo que corresponde a un proceso de software en cascada, ya
que se regresa una y otra vez a etapas anteriores, hasta que se pueda llegar al final del diagrama.
2.1 ¿Qué es la programación? 34

Figura 2.1 Proceso del software

Especificación

Análisis y diseño

Implementación

Validación

Mantenimiento

Refinamiento y extensión

Especificación del problema: Se nos presenta el enunciado del problema y de-


bemos determinar de manera precisa las especificaciones: de dónde partimos
(con qué entradas contamos) y a dónde queremos llegar (cuál es el resulta-
do que deseamos obtener). Como producto de esta etapa producimos tres
incisos:
a. Enunciado preciso del problema.
b. Entradas.
c. Salidas.
35 El proceso del software

Análisis y diseño del algoritmo: Planteamos la manera en que vamos a trans-


formar los datos de entrada para obtener el resultado que buscamos y pro-
cedemos a elaborar un modelo de la solución. Muchas veces este modelo
involucra varios pasos intermedios (estados de los datos) o, más que un re-
sultado concreto, buscamos un cierto comportamiento, como en el caso de
un juego o una simulación –como la de un reloj–. En estos últimos casos de-
beremos pensar en procesos sucesivos que nos lleven de un estado de cosas
(estado de los datos) al estado inmediato sucesor –fotos instantáneas– y cuál
o cuáles son las transformaciones de los datos que nos producen el siguiente
estado.
Dependiendo del ambiente (dominio) de nuestro problema, las soluciones
que diseñemos deberán tener distintas caracterı́sticas. La primera regla, que
comparten todos los dominios es:

a) La solución debe ser correcta, eficiente y efectiva.

Es claro que toda solución debe ser correcta, esto es, resolver el problema.
Una solución es eficiente si usa una cantidad razonable de recursos y se
ejecuta en un tiempo razonable. Respecto a esta caracterı́stica, la única
excepción posible a esta regla se presenta si estamos haciendo un programa
para ayudarnos a calcular algo o para sacarnos de un brete momentáneo o
coyuntural, el programa se va a utilizar pocas veces en un lapso corto de
tiempo; tal vez hasta podemos eliminar la caracterı́stica de que sea eficiente.
En los primeros años de las computadoras, casi todos los programas eran
de este tipo: la gente los hacı́a para sı́ mismos, o para un grupo reducido
que estaba muy al tanto de lo que estaba pasando. Que un programa sea
efectivo quiere decir que el programa termina.
Hoy en dı́a, en que las computadoras están en todo, la mayorı́a de la gente
involucrada haciendo programas los hace para otros. Además, el tamaño
de los sistemas ha crecido tanto que ya casi nadie es el “dueño” de sus
programas, sino que se trabaja en el contexto de proyectos grandes, con
mucha gente involucrada. En este tipo de situaciones, que hoy en dı́a son
más la regla que la excepción, se requiere además que los programas:

b) Sean modulares. Se puedan trazar claramente las fronteras entre pedazos


del programa (o sistema) que atacan un cierto subproblema, para que la
tarea se pueda repartir.
c) Tengan un bajo nivel de acoplamiento. Esta propiedad se refiere a que
utilicen lo menos posible del mundo exterior y entreguen lo mı́nimo po-
2.1 ¿Qué es la programación? 36

sible: que haya poco tráfico entre los módulos, de tal manera que haya la
posibilidad de reutilizarlos.
d) Alta cohesión, que se refiere al hecho de que todo lo que esté relacionado
(funciones, datos) se encuentren juntos, para que sean fáciles de localizar,
entender y modificar.

Implementación o construcción del modelo: En esta etapa deberemos tra-


ducir nuestro algoritmo al lenguaje de programación que hayamos elegido.
A esta etapa se le conoce también como de codificación. Ésta no es una labor
muy difı́cil, si es que tenemos un diseño que siga la filosofı́a2 del lenguaje de
programación.
Durante las etapas de diseño e implementación se debe asegurar la do-
cumentación del programa, que algunas veces aparece como una etapa
del proceso. Sin embargo, esto no es –o no deberı́a ser– una etapa separada
del proceso, ya que lo ideal es que conforme se va progresando se vaya docu-
mentando el programa (o en su caso el diseño). Hoy en dı́a existen muchos
paquetes que ayudan a llevar a cabo el diseño y que apoyan la elaboración de
la documentación, por lo que actualmente es imperdonable que falte docu-
mentación en los programas. En el presente, en que los programas se hacen
en un 90 % de los casos para otros, es muy importante que el programa lo
pueda entender cualquier lector humano; esto se logra, la mayorı́a de las
veces, mediante la documentación3 .

Prueba y validación: Debemos tener la seguridad de que nuestro programa ha-


ce lo que se supone debe hacer. Para esto hay pruebas informales, que con-
sisten en presentarle al programa distintos conjuntos de datos y verificar que
el programa hace lo que tiene que hacer. Estas pruebas deben incluir con-
juntos de datos erróneos, para verificar que el programa sabe “defenderse”
en situaciones anómalas. También existen módulos especı́ficos en ciertos len-
guajes de programación, como JUnit de Java, que permite elaborar pruebas
unitarias para nuestro código.
La validación de los programas conlleva demostraciones matemáticas de que
los enunciados cambian el estado de los datos de la manera que se busca.
2
Con esto nos referimos a la manera como el lenguaje de programación interpreta el mundo:
por procedimientos, orientado a objetos, funcional, lógico. A esto le llamamos paradigmas de
programación.
3
En el contexto de aprender a programar la documentación es muy importante para el que va
a calificar el programa y para el que está aprendiendo, ya que le permite recuperar el contexto
en el que resolvió algo de cierta manera.
37 El proceso del software

Mantenimiento: La actividad que mayor costo representa hoy en dı́a es la del


mantenimiento de los sistemas. Tenemos dos tipos de mantenimiento: correc-
tivo y extensivo. El correctivo tiene que ver con situaciones que el programa
o sistema no está resolviendo de manera adecuada. El mantenimiento exten-
sivo tiene que ver con extender las capacidades del programa o sistema para
que enfrente conjuntos nuevos de datos o entregue resultados adicionales.
Sin una documentación adecuada, estas labores no se pueden llevar a cabo.
Sin un diseño correcto, es prácticamente imposible extender un sistema.

Refinamiento y extensión: Esta etapa generalmente la llevan a cabo personas


distintas a las que diseñaron el sistema. Se busca que cuando se extiende un
sistema no se tape un hoyo haciendo otro. La modularidad ayuda, pues se
entiende mejor el funcionamiento del programa si se ataca por módulos. Pe-
ro esta propiedad debe tener, además, la propiedad de encapsulamiento, que
consiste en que cada módulo tiene perfectamente delimitado su campo de
acción, la comunicación entre módulos es muy controlada y una implemen-
tación interna (modo de funcionar) que pueda ser cambiada sin que altere
su comportamiento y sin que haya que cambiar nada más.

Estas etapas, como ya mencionamos en algunas de ellas, no se presentan for-


zosamente en ese orden. Más aún, muchas veces se da por terminada una de ellas,
pero al proceder a la siguiente surgen problemas que obligan a regresar a etapas
anteriores a modificar el producto o, de plano, rehacerlo. Queremos insistir en
la importancia de tener un buen análisis y un buen diseño, no importa cuánto
tiempo nos lleve: ésta es la llave para que en etapas posteriores no tengamos que
regresar a rehacer, o nos veamos en la necesidad de tirar trabajo ya hecho.
Pasemos a detallar un poco más algunas de las etapas de las que hablamos.

Especificación
Una buena especificación, sea formal o no, hace hincapié, antes que nada,
en cuál es el resultado que se desea obtener. Este resultado puede tomar muy
distintas formas. Digamos que el cómputo corresponde a un modelo de un sistema
(la instrumentación o implementación del modelo).
Podemos entonces hablar de los estados por los que pasa ese sistema, donde un
estado corresponde a los valores posibles que toman las variables. Por ejemplo, si
tenemos las variables x, y, z, que participan en un determinado cálculo, un posible
estado serı́a:
t x  5, y  7, z  9 u
Si tenemos la especificación de un programa (rutina) que intercambie los valores
2.1 ¿Qué es la programación? 38

de dos variables x, y, podemos pensarlo ası́:

t x  K1 , y  K2 u
es el estado inicial (con los valores que empieza el proceso), mientras que

t x  K2 , y  K1 u
corresponde al estado final deseado. Podemos adelantar que una manera de lograr
que nuestro modelo pase del estado inicial al estado final es si a x le damos el
valor de K2 (el valor que tiene y al empezar) y a y le damos el valor que tenı́a x.
Podemos representar este proceso de la siguiente manera:

t x  K1 , y  K2 u // estado inicial
A x ponerle K2 // Proceso
A y ponerle K1 // Proceso
t x  K2 , y  K1 u // estado final

y podemos garantizar que nuestras operaciones cumplen con llevar al modelo al


estado final. Sin embargo, las operaciones que estamos llevando a cabo están con-
siderando a K1 y K2 valores constantes. Un proceso más general serı́a el siguiente:

t x  K1 y  K2 u // estado inicial
En t copia el valor que tiene x // Proceso
En x copia el valor que tiene y // Proceso
t x  K2 , y  K2 , t  K1 u estado intermedio
// ¡En estos momentos x e y valen lo mismo!
En y copia el valor de t // Proceso
t x  K 2 , y  K1 u // estado final

y este proceso funciona no importando qué valores iniciales dimos para x e y.


Resumiendo, un estado de un proceso, cómputo o modelo es una lista de va-
riables, cada una de ellas con un cierto valor.
Una especificación de un problema es la descripción del problema, que como
mı́nimo debe tener el estado final del cómputo. El estado inicial puede ser fijado
a partir del estado final (determinando qué se requiere para poder alcanzar ese
estado), o bien puede darse también como parte de la especificación.

Análisis y diseño
Podemos decir, sin temor a equivocarnos, que la etapa de diseño es la más
importante del proceso. Si ésta se lleva a cabo adecuadamente, las otras etapas
39 El proceso del software

se simplifican notoriamente. La parte difı́cil en la elaboración de un programa


de computadora es el análisis del problema (definir exactamente qué se desea) y
el diseño de la solución (plantear cómo vamos a obtener lo que deseamos). Para
esta actividad se requiere de creatividad, inteligencia y paciencia. La experiencia
juega un papel muy importante en el análisis y diseño. Dado que la experiencia
se debe adquirir, es conveniente contar con una metodologı́a que nos permita ir
construyendo esa experiencia.
Ası́ como hay diversidad en los seres humanos, ası́ hay maneras distintas de
analizar y resolver un problema. En esa búsqueda por “automatizar” o matemati-
zar el proceso de razonamiento, se buscan métodos o metodologı́as que nos lleven
desde la especificación de un problema hasta su mantenimiento, de la mejor ma-
nera posible. El principio fundamental que se sigue para analizar y diseñar una
solución es el de divide y vencerás, reconociendo que si un problema resulta de-
masiado complejo para que lo ataquemos, debemos partirlo en varios problemas
de menor magnitud. Podemos reconocer tres vertientes importantes en cuanto a
las maneras de dividir el problema:

a) Programación o análisis estructurado, que impulsa la descomposición del pro-


blema en términos de acciones, convergiendo todas ellas en un conjunto de
datos, presentes todo el tiempo. La división se hace en términos del proceso,
reconociendo distintas etapas en el mismo. Dado que el énfasis es en el proceso,
cada módulo del sistema corresponde a un paso o etapa del proceso. Cuando
usamos este enfoque perdemos la posibilidad de “encapsular”, pues los datos se
encuentran disponibles para todo mundo, y cada quien pasa y les mete mano.
Además, hay problemas que son difı́ciles de analizar en términos de “etapas”.
Por ejemplo, los juegos de computadora estilo aventura, las simulaciones de
sistemas biológicos, el sistema de control de un dispositivo quı́mico, un sistema
operativo. Sin embargo, esta metodologı́a es muy adecuada para diseñar, como
lo acabamos de mencionar, un cierto proceso que se lleva a cabo de manera
secuencial.

b) Análisis y diseño orientado a objetos. El análisis orientado a objetos pretende


otro enfoque, partiendo al problema de acuerdo a los objetos presentes. De-
termina cuáles son las responsabilidades de cada objeto y qué le toca hacer a
cada quién.

c) Diseño funcional, que involucra generalmente recursión, y que denota el diseño


como la aplicación sucesiva de funciones a los datos de entrada hasta obtener
el resultado final.
2.1 ¿Qué es la programación? 40

Implementación del modelo


Para la programación, como ya mencionamos, utilizaremos Java y aprovecha-
remos la herramienta JavaDoc para que la documentación se vaya haciendo, como
es deseable, durante la codificación.

Mantenimiento
Porque se trata de un curso, no nos veremos expuestos a darle mantenimiento
a nuestros programas, excepto cuando detecten que algo no funciona bien. En
las sesiones de laboratorio, sin embargo, tendrán que trabajar con programas ya
hechos y extenderlos, lo que tiene que ver con el mantenimiento. También es la
filosofı́a de este material empezar con una aplicación básica e irla extendiendo en
poderı́o y sofisticación.

2.1.1. Conceptos en la orientación a objetos

Al hacer el análisis de nuestro problema, como ya mencionamos, trataremos


de dividir la solución en tantas partes como sea posible, de tal manera que cada
parte sea fácil de entender y diseñar. La manera de dividir el problema será en
términos de actores y sus respectivas responsabilidades (o facultades): qué puede
y debe hacer cada actor para contribuir a la solución. Cada uno de estos actores
corresponde a un objeto. Agrupamos y abstraemos a los objetos presentes en clases.
Cada clase cumple con:
Tiene ciertas caracterı́sticas.
Funciona de determinada manera.
Quedan agrupados en una misma clase aquellos objetos que presentan las
mismas caracterı́sticas y funcionan de la misma manera.
En el diseño orientado a objetos, entonces, lo primero que tenemos que hacer
es clasificar nuestro problema: encontrar las distintas clases involucradas en el
mismo.
Las clases nos proporcionan un patrón de conocimiento –qué es lo que debe
saber , conocer, recordar cada objeto de la clase– y un patrón de comportamiento:
nos dicen qué y cómo se puede hacer con los datos de la clase. Es como el guión
de una obra de teatro, ya que la obra no se nos presenta hasta en tanto no haya
actores. Los actores son los objetos, que son ejemplares o instancias 4 de las clases
(representantes de las clases).
4
instancia es una traducción literal que no existe en español.
41 El proceso del software

Al analizar un problema debemos tratar de identificar a los objetos involucra-


dos. Una vez que tengamos una lista de los objetos (agrupando datos que tienen
propósitos similares, por ejemplo), debemos abstraer, encontrando caracterı́sticas
comunes, para definir las clases que requerimos.
Distinguimos a un objeto de otro de la misma clase por su nombre –identidad
o identificador –. Nos interesa de un objeto dado:

i. Su estado: cuál es el valor de sus atributos.


ii. Su conducta:
Qué cosas sabe hacer.
Cómo va a reaccionar cuando se le hagan solicitudes.

El número y tipo de atributos (variables) con que cuenta un objeto está deter-
minado por la definición de la clase a la que pertenece, aunque el estado está de-
terminado por cada objeto, ya que cada objeto es capaz de almacenar su propia
información. Lo correspondiente a (ii) está dado por la definición de la clase, que
nos da un patrón de conducta.
Tratando de aclarar un poco, pensemos en lo que se conoce como sistema
cliente/servidor. Cliente es aquél que pide, compra, solicita algo: un servicio, un
valor, un trabajo. Servidor es aquél que provee lo que se le está pidiendo. Esta
relación de cliente/servidor, sin embargo, no es estática. El servidor puede tomar
el papel de cliente y viceversa.
Lo que le interesa al cliente es que el servidor le proporcione aquello que el
cliente está pidiendo. No le importa cómo se las arregla el servidor para hacerlo.
Si el cliente le pide al servidor algo que el servidor no sabe hacer, que no reconoce,
simplemente lo ignora, o le contesta que eso no lo sabe hacer.
El análisis orientado a objetos pretende reconocer a los posibles clientes y servi-
dores del modelo, y las responsabilidades de cada quien. Divide la responsabilidad
global del proceso entre distintos objetos.
Un concepto muy importante en la orientación a objetos es el encapsulamiento
de la información. Esto quiere decir que cada objeto es dueño de sus datos y sus
funciones, y puede o no permitir que objetos de otras clases ajenas vean o utilicen
sus recursos.
Un objeto entonces tiene la propiedad de que encapsula tanto a los procesos
(funciones) como a los datos. Esto es, conoce cierta información y sabe cómo llevar
a cabo determinadas operaciones. La ventaja del encapsulamiento es que en el
momento de diseñar nos va a permitir trazar una lı́nea alrededor de operaciones y
datos relacionados y tratarlos como una cápsula, sin preocuparnos en ese momento
de cómo funciona, sino únicamente de qué es capaz de hacer.
2.1 ¿Qué es la programación? 42

En el caso de los objetos, la cápsula alrededor del mismo oculta al exterior la


manera en que el objeto trabaja, el cómo. Cada objeto tiene una interfaz pública
y una representación privada. Esto nos permite hacer abstracciones más fácil y
elaborar modelos más sencillos, pues lo que tomamos en cuenta del objeto es
exclusivamente su interfaz; posponemos la preocupación por su representación
privada y por el cómo.
Públicamente un objeto “anuncia” sus habilidades: “puedo hacer estas cosas”,
“puedo decir estas cosas”; pero no dice cómo es que las puede hacer o cómo es que
sabe las cosas. Los objetos actúan como un buen jefe cuando le solicitan a otro
objeto que haga algo: simplemente le hacen la solicitud y lo dejan en paz para que
haga lo que tiene que hacer; no se queda ahı́ mirando sobre su hombro mientras
lo hace.
El encapsulamiento y el ocultamiento de la información colaboran para aislar
a una parte del sistema de otras, permitiendo de esta manera la modificación y
extensión del mismo sin el riesgo de introducir efectos colaterales no deseados.
Para determinar cuáles son los objetos presentes en un sistema, se procede de la
siguiente manera:

1. Se determina cuáles funcionalidades e información están relacionadas y de-


ben permanecer juntas, y se encapsulan en una clase.
2. Entonces se decide cuáles funcionalidades e información se le van a solicitar a
representantes de esta clase (qué servicios va a prestar). Éstos se mantienen
públicos, mientras que el resto se esconde en el interior de los objetos de esa
clase.

Esto se logra mediante reglas de acceso, que pueden ser de alguno de los tipos
que siguen:

Público: Se permite el acceso a objetos de cualquier otra clase.


Privado: Sólo se permite el acceso a objetos de la misma clase.

En algunos lenguajes de programación se permiten otros tipos de acceso. Por


ejemplo, en Java también tenemos los siguientes, pero que no pretendemos dejar
claros por el momento:

Paquete: Se permite el acceso a objetos que están agrupados en el mismo paquete


(generalmente un sistema o aplicación).
Protegido: Se permite el acceso a objetos de clases que hereden de esta clase.

Veamos la terminologı́a involucrada en el diseño orientado a objetos:


43 El proceso del software

Atributos

Algunos sistemas o lenguajes orientados a objetos llaman a éstos variables


propias, variables miembro o variables de estado. Se refieren a los valores que
debe recordar cada objeto y que describe el estado del objeto. Cada objeto puede
tener un valor distinto en cada uno de estos atributos. Como uno de sus nombres
indica (variables de estado), conforman el estado del objeto, que puede cambiar
durante la ejecución de la aplicación.

Mensajes (messages)

Un objeto le pide un servicio a otro mandándole un mensaje. A esta acción le


llamamos el envı́o de un mensaje y es la única manera en que un objeto se puede
comunicar con otro.
Un mensaje consiste del nombre de una operación y los argumentos que la
operación requiere. La solicitud no especifica cómo debe ser satisfecha.

Comportamiento o conducta (behaviour )

El conjunto de mensajes a los que un objeto puede responder es lo que se


conoce como la conducta o el comportamiento del objeto.
Al nombre de la operación en el mensaje le llamamos el nombre del mensaje.

Métodos (methods)

Cuando un objeto recibe un mensaje, ejecuta la operación que se le solicita


mediante la ejecución de un método. Un método es un algoritmo paso a paso
que se ejecuta como respuesta a un mensaje cuyo nombre es el mismo que el del
método. Para que un método pueda ser invocado desde un objeto de otra clase,
debe ser público. En el caso de algunos lenguajes de programación, a los méto-
dos se les llama funciones miembro (member functions), porque son funciones
(procedimientos) que están definidos en, son miembros de, la clase.
2.1 ¿Qué es la programación? 44

Clases (classes)

Si dos objetos presentan el mismo comportamiento, decimos que pertenecen a


la misma clase. Una clase es una especificación genérica para un número arbitrario
de objetos similares. Las clases permiten construir una taxonomı́a de los objetos
en un nivel abstracto, conceptual. Nos permiten describir a un grupo de objetos.
Por ejemplo, cuando describimos a los seres humanos damos las caracterı́sticas
que les son comunes. Cada ser humano es un objeto que pertenece a esa clase. Hay
que insistir en que las clases corresponden únicamente a descripciones de objetos,
no tienen existencia en sı́ mismas.

Ejemplares (instances)

Las instancias corresponden, de alguna manera, a los “ejemplares” que pode-


mos exhibir de una clase dada. Por ejemplo, describimos a los seres humanos y
decimos que Fulanito es un ejemplar de la clase de seres humanos: existe, tiene
vida propia, le podemos pedir a Fulanito que haga cosas. Fulanito es un objeto
de la clase de seres humanos. Fulanito es el nombre (identificador, identidad) del
ejemplar u objeto. Su comportamiento es el descrito por los métodos de su clase.
Está en un cierto estado, donde el estado es el conjunto de datos (caracterı́sticas)
junto con valores particulares.
En cambio, si hablamos de un ser humano en abstracto, de la categorı́a que
describe a todos los seres humanos, no podremos decir cuál es su altura o su color
de pelo, sino únicamente cuáles son los colores de pelo válido. También sabemos
que come, pero no podemos decir si con las manos, utilizando una cuchara o
una tortilla. Un ejemplar tiene caracterı́sticas concretas, mientras que una clase
describe todas las posibles formas que puede tomar cada una de las caracterı́sticas.
Un mismo objeto puede pasar por distintos estados. Por ejemplo, podemos
definir que las caracterı́sticas de un ser humano incluyen: estado de conciencia,
estado de ánimo, posición. Fulanito puede estar dormido o despierto, contento o
triste, parado o sentado, correspondiendo esto a cada una de las caracterı́sticas (o
variables). Sin embargo, hay variables entre las que corresponden a un objeto que
si bien cambian de un objeto a otro (de una instancia a otra), una vez definidas
en el objeto particular ya no cambian, son invariantes. Por ejemplo, el color de
los ojos, el género, la forma de la nariz5 .
5
No estamos considerando, por supuesto, la posibilidad de cirugı́as, implantes o pupilentes
que puedan cambiar el estado de un ser humano.
45 El proceso del software

Herencia (inheritance)

Es frecuente encontrar una familia de objetos (o clases) que tienen un núcleo


en común, pero que difieren cada uno de ellos por algún atributo, una o más
funciones. Cuando esto sucede debemos reconocer al núcleo original, identificando
a una clase abstracta (o súper-clase, no forzosamente abstracta) a partir de la
cual cada una de las clases de la familia son una extensión. A esta caracterı́stica
se le conoce como herencia: las clases en la familia de la que hablamos, heredan
de la clase abstracta o clase base, remedando un árbol como el que se muestra en
la figura 2.2. En esta figura las clases abstractas o base se presentan encuadradas
con lı́neas intermitentes, mientras que las subclases se presentan encuadradas con
lı́nea continua. A este tipo de esquema le llamamos jerarquı́a de clases 6 . Cuando
una clase es abstracta queremos decir que no tenemos suficiente información para
construir objetos completos de ella. Por ejemplo, la clase Elementos geométricos
no da suficiente información para construir a un objeto de la misma, pues no dice
su forma precisa.

Figura 2.2 Árbol de herencia en clases

Elementos geométricos

punto lı́nea Volumen

recta curva cubo cilindro esfera pirámide


Superficie

cuadrado rectángulo cı́rculo elipse

6
Vale la pena notar que los niveles en el árbol no están dibujados adecuadamente, pues clases
en el mismo nivel jerárquico no están alineadas en la figuras.
2.2 Diseño orientado a objetos 46

Polimorfismo (polymorphism)

Dado que tenemos la posibilidad de agrupar a las clases por “familias”, que-
remos la posibilidad de que, dependiendo de cuál de los herederos se trate, una
función determinada se lleve a cabo de manera “personal” a la clase. Por ejemplo,
si tuviéramos una familia, la función de arreglarse se deberı́a llevar a cabo de
distinta manera para la abuela que para la nieta. Pero la función se llama igual:
arreglarse. De la misma manera en orientación a objetos, conforme definimos he-
rencia podemos modificar el comportamiento de un cierto método, para que tome
en consideración los atributos adicionales de la clase heredera. A esto, el que el
mismo nombre de método pueda tener un significado distinto dependiendo de la
clase a la que pertenece el objeto particular que lo invoca, es a lo que se llama
polimorfismo –tomar varias formas–.

2.1.2. Solución del problema

En resumen, presentados ante un problema, estos son los pasos que debemos
realizar:
1. Escribir de manera clara los requisitos y las especificaciones del problema.
2. Identificar las distintas clases que colaboran en la solución del problema y la
relación entre ellas; asignar a cada clase las responsabilidades correspondien-
tes en cuanto a información y proceso (atributos y métodos respectivamen-
te); identificar las interacciones necesarias entre los objetos de las distintas
clases (diseño).
3. Codificar el diseño en un lenguaje de programación, en este caso Java.
4. Compilar y depurar el programa.
5. Probarlo con distintos juegos de datos.
De lo que hemos visto, la parte más importante del proceso va a ser el análisis
y el diseño, ası́ que vamos a hablar de él con más detalle.

2.2 Diseño orientado a objetos


El diseño orientado a objetos es el proceso mediante el cual se transforman las
especificaciones (o requerimientos) de un sistema en una especificación detallada
de objetos. Esta última especificación debe incluir una descripción completa de
47 El proceso del software

los papeles y responsabilidades de cada objeto y la manera en que los objetos se


comunican entre sı́.
Al principio, el proceso de diseño orientado a objetos es exploratorio. El di-
señador busca clases, agrupando de distintas maneras para encontrar la manera
más natural y razonable de abstraer (modelar) el sistema. Inicialmente consiste
de los siguientes pasos:
1. Determina (encuentra) las clases en tu sistema.
2. Determina qué operaciones son responsabilidad de cada clase, y cuáles son
los conocimientos que la clase debe mantener o tener presentes para poder
cumplir con sus responsabilidades.
3. Determina las formas en las que los objetos colaboran con otros objetos para
delegar sus responsabilidades.
Estos pasos producen:
una lista de clases dentro de tu aplicación;
una descripción del conocimiento y operaciones que son responsabilidad de
cada clase; y
una descripción de la colaboración entre clases.
Una vez que se tiene este esquema, conviene tratar de precisar una jerarquı́a
entre las clases que definimos. Esta jerarquı́a establece las relaciones de herencia
entre las clases. Dependiendo de la complejidad del diseño, podemos tener anida-
dos varios niveles de encapsulamiento. Si nos encontramos con varias clases a las
que conceptualizamos muy relacionadas, podemos hablar entonces de subsiste-
mas. Un subsistema, desde el exterior, es visto de la misma manera que una clase.
Desde adentro, es un programa en miniatura, que presenta su propia clasificación
y estructura. Las clases proporcionan mecanismos para estructurar la aplicación
de tal manera que sea reutilizable.
Si bien suena sencillo eso de “determina las clases en tu aplicación”, en la vida
real éste es un proceso no tan directo como pudiera parecer. Revisemos con un
poco de más detalle estos procesos:
1. Determina las clases en tu sistema (encuentra los objetos).
Para determinar cuáles son los objetos en tu sistema, debes tener muy claro
cuál es el objetivo del mismo. ¿Qué debe lograr el sistema? ¿Cuál es la
conducta que está claramente fuera del sistema? La primera pregunta se
responde dando una especificación completa del problema. En un principio
daremos descripciones narrativas y nuestro primer paso deberá consistir en
dividir el problema en subproblemas, identificando las clases.
En esta descripción narrativa, existe una relación entre los sustantivos o
nombres y los objetos (o clases). Una primera aproximación puede ser, en-
tonces, hacer una lista de todos los sustantivos que aparezcan en la especifi-
2.2 Diseño orientado a objetos 48

cación narrativa. Una vez que se tiene esta lista, debemos intentar reconocer
similitudes, herencia, interrelaciones. Debemos clasificar a nuestros objetos
para determinar cuáles son las clases que vamos a necesitar.
Las probabilidades de éxito en el diseño del sistema son directamente pro-
porcionales a la exactitud y precisión con que hagamos esta parte del diseño.
Si este primer paso no está bien dado, el modelo que obtengamos a partir de
él no va a ser útil y vamos a tener que regresar posteriormente a “parcharlo”.
2. Determina las responsabilidades de las clases definidas (métodos y estruc-
turas de datos).
Determinar las responsabilidades de un objeto involucra dos preguntas:
¿Qué es lo que el objeto tiene que saber de tal manera que pueda
realizar las tareas que tiene encomendadas?
¿Cuáles son los pasos, en la dirección del objetivo final del sistema, en
los que participa el objeto?
Respondemos a esta pregunta en términos de responsabilidades. Posponemos
lo más posible la definición de cómo cumple un objeto con sus responsabili-
dades. En esta etapa del diseño nos interesa qué acciones se tienen que llevar
a cabo y quién es el responsable de hacerlo.
De la misma manera que existe una cierta relación entre los sustantivos de
la especificación y las clases, podemos asociar los verbos de la especificación
a los métodos o responsabilidades. Si hacemos una lista de las responsabili-
dades y las asociamos a los objetos, tenemos ya un buen punto de partida
para nuestro modelo.
Un objeto tiene cinco tipos de métodos (o funciones):
Métodos constructores, que son los encargados de la creación de los
objetos, ası́ como de su inicialización (establecer el estado inicial).
Métodos de implementación, que son aquellos que representan a los
servicios que puede dar un objeto de esa clase.
Métodos de acceso que proporcionan información respecto al estado del
objeto.
Métodos auxiliares que requiere el objeto para poder dar sus servicios,
pero que no interactúan con otros objetos o clases.
Métodos mutantes (de actualización y manipuladores), que modifican
el estado del objeto.
3. Determina su colaboración (mensajes). En esta subdivisión nos interesa res-
ponder las siguientes preguntas respecto a cada una de las clases definidas:
49 El proceso del software

¿Con qué otros objetos tiene que colaborar para poder cumplir con sus
responsabilidades (a quién le puede delegar)?
¿Qué objetos en el sistema poseen información que necesita este objeto?
¿Qué otros objetos saben cómo llevar a cabo alguna operación que
requiere?
¿En qué consiste exactamente la colaboración entre objetos?
Es importante tener varios objetos que colaboran entre sı́. De otra manera,
el programa (o aplicación) va a consistir de un objeto enorme que hace todo.
En este paso, aunque no lo hemos mencionado, tenemos que involucrarnos
ya con el cómo cumple cada objeto con su responsabilidad, aunque no to-
davı́a a mucho nivel de detalle. Un aspecto muy importante es el determinar
dónde es que se inician las acciones. En el esquema de cliente/servidor del
que hemos estado hablando, en este punto se toman las decisiones de si el
objeto subcontrata parte de su proceso, si es subcontratado por otro objeto,
etcétera. Es importante recalcar que mientras en la vida real algunos de los
objetos tienen habilidad para iniciar por sı́ mismos su trabajo, en el mundo
de la programación orientada a objetos esto no es ası́: se requiere de un
agente que inicie la acción, que ponga en movimiento al sistema.
Es necesario en esta etapa describir con mucha precisión cuáles son las en-
tradas (input) y salidas (output) de cada solicitud y la manera que cada
objeto va a tener de reaccionar frente a una solicitud. En teorı́a, un objeto
siempre da una respuesta cuando se le solicita un servicio. Esta respuesta
puede ser
No sabe hacer lo que le piden, no lo reconoce.
Un valor o dato que posee.
La realización de un proceso
La manera como estas respuestas se manifiestan van a cambiar de sistema
a sistema (de lenguaje a lenguaje).
4. Determina la accesibilidad de las funciones y datos. Una vez que se tiene
clasificado al sistema, es importante perseguir el principio de ocultamiento
de la información. Esto consiste en decidir, para cada clase, cuáles de sus
funciones y sus datos van a estar disponibles, públicos, y cuáles van a estar
ocultos dentro de los objetos de la clase. Es claro que los métodos o funciones
forman parte de la sección pública, pues van a ser solicitados por otros
objetos. También es claro que los datos deben permanecer ocultos, pues
queremos que el objeto mismo sea el único que puede manipular su propio
estado. No queremos que otra clase u objeto tenga acceso a los valores del
objeto, sino que el mismo objeto controle esta interacción.
2.3 Diseño estructurado 50

Sin embargo, a veces requerimos de funciones que sólo el objeto necesita o


usa. En estos casos, estas funciones las vamos a colocar también en el espacio
privado de la clase, buscando que el acoplamiento entre clases sea mı́nimo:
si nadie requiere de ese servicio, más que el mismo objeto, ¿para qué ponerlo
disponible?
Si bien tratamos de dar una metodologı́a para el diseño orientado a objetos, es
imposible hacerlo de manera completa en unas cuantas páginas (hay cursos que
se dedican únicamente a este tema). Lo que se menciona arriba son únicamente
indicaciones generales de cómo abordar el problema, aprovechando la intuición
natural que todos poseemos. Conforme avancemos en el material, iremos exten-
diendo también la metodologı́a.

2.2.1. Tarjetas de responsabilidades


Como resultado de este análisis elaboraremos, para cada clase definida, lo
que se conoce como una tarjeta de responsabilidades. Esta tarjeta registrará los
atributos, responsabilidades y colaboración que lleva a cabo esa clase, y una breve
descripción del objetivo de cada atributo y de cada método.

2.3 Diseño estructurado

Como ya mencionamos antes, para diseñar cada uno de los métodos o funciones
propias de un sistema debemos recurrir a otro tipo de análisis que no corresponde
a la orientación a objetos. Esto se debe fundamentalmente a que dentro de un
método debemos llevar a cabo un algoritmo que nos lleve desde un estado inicial a
otro final, pero donde no existe colaboración o responsabilidades, sino simplemente
una serie de tareas a ejecutar en un cierto orden.
Tenemos cuatro maneras de organizar a los enunciados o lı́neas de un algoritmo:

Secuencial, donde la ejecución prosigue, en orden, con cada lı́nea, una después
de la otra y siguiendo la organización fı́sica. Por ejemplo:
1 pon 1 en x
2 suma 2 a x
3 copia x a y

se ejecutarı́a exactamente en el orden en que están listadas.


51 El proceso del software

Iteración, que marca a un cierto conjunto de enunciados secuenciales e indica la


manera en que se van a repetir. Por ejemplo:
1 pon 1 en x
2 R e p i t e 10 v e c e s d e s d e e l e n u n c i a d o 3 h a s t a e l 5 :
3 suma 2 a x
4 copia x a y
5 E s c r i b e e l v a l o r de x

En este caso, podemos decir que el estado inicial de las variables al llegar a
la iteración es con x valiendo 1 y con y con un valor indeterminado. ¿Cuál
es el estado final, al salir de la iteración?
La manera como indicamos el grupo de enunciados a repetirse es dando una
sangrı́a mayor a este grupo; siguiendo esta convención, el enunciado de la
lı́nea 2 podrı́a simplemente ser
2 R e p i t e 10 v e c e s :

y el solo hecho de que los enunciados 3 a 5 aparecen con mayor sangrı́a da


la pauta para que ésos sean los enunciados a repetirse.

Ejecución condicional, que se refiere a elegir una de dos o más opciones de


grupos de enunciados. Por ejemplo:
1 p o n e r un v a l o r a r b i t r a r i o a y , e n t r e 0 y 9999
2 Si x e s mayor que 1 :
3 d i v i d e a y e n t r e x y c o l o c a e l r e s u l t a d o en z
4 m u l t i p l i c a a z p o r 13
5 e s c r i b e e l v a l o r de z
6 Si x no e s mayor que 1 :
7 m u l t i p l i c a a y p o r 61
8 e s c r i b e e l v a l o r de y

En este caso planteamos dos opciones, una para cuando el estado inicial,
antes de entrar a la ejecución condicional, sea con x teniendo un valor mayor
que 1, y la otra para cuando x tenga un valor menor que 1 (que pudiera ser 0).

Recursividad, que es cuando un enunciado está escrito en términos de sı́ mismo,


como es el caso de la definición del factorial de un número:
$
&n  pn  1q! para n ¡ 1
'
n! 
'
% 1 para n  1
2.3 Diseño estructurado 52

Toda solución algorı́tmica que demos, sobre todo si seguimos un diseño es-
tructurado, deberá estar dado en términos de estas estructuras de control. El
problema central en diseño consiste en decidir cuáles de estas estructuras utili-
zar, cómo agrupar enunciados y cómo organizar, en general, los enunciados del
método.
Una parte importante de todo tipo de diseño es la notación que se utiliza
para expresar los resultados o modelos. Al describir las estructuras de control
utilizamos lo que se conoce como pseudocódigo, pues escribimos en un lenguaje
parecido al español las acciones a realizar. Esta notación, si bien es clara, resulta
fácil una vez que tenemos definidas ya nuestras estructuras de control, pero no
nos ayuda realmente a diseñar. Para diseñar utilizaremos lo que se conoce como
la metodologı́a de Warnier-Orr, cuya caracterı́stica principal es que es un diseño
controlado por los datos, i.e. que las estructuras de control están dadas por las
estructuras que guardan los datos. Además, el diseño parte siempre desde el estado
final del problema (qué es lo que queremos obtener) y va definiendo pequeños pasos
que van transformando a los datos hacia el estado inicial del problema (qué es lo
que sabemos antes de empezar a ejecutar el proceso).
Empecemos por ver la notación que utiliza el método de Warnier-Orr, y dado
que es un método dirigido por los datos, veamos la notación para representar
grupos de datos, que al igual que los enunciados, tienen las mismas 4 formas
de organizarse: secuencial, iterativa, condicional o recursiva. Por supuesto que
también debemos denotar la jerarquı́a de la información, donde este concepto se
refiere a la relación que guarda la información entre sı́. Representa a los datos
con una notación muy parecida a la de teorı́a de conjuntos, utilizando llaves para
denotar a los conjuntos de datos (o enunciados). Sin embargo, cada conjunto puede
ser “refinado” con una “ampliación” de su descripción, que se encuentra siempre
a la derecha de la llave. Otro aspecto muy importante es que en el caso de los
“conjuntos” de Warnier-Orr el orden dentro de cada conjunto es muy importante.
La manera en que el método de Warnier-Orr representa estos conceptos se explica
a continuación:

Jerarquı́a
Abre llaves. El “nombre” de la llave es el objeto de mayor jerarquı́a e identifica
al subconjunto de datos que se encuentran a la derecha de la llave. Decimos
entonces que lo que se encuentra a la derecha de la llave “refina” (explica con
mayor detalle) al “nombre” de la llave. Veamos la figura 2.3 en la página opuesta.

Jerarquı́a: en la figura figura 2.3 en la página opuesta, “nombre” agrupa


a descr1 , descr2 ,. . . ,descrn ; decimos entonces que las descripciones son un
53 El proceso del software

refinamiento de “nombre”, y que “nombre” es de mayor jerarquı́a que las


descripciones.

Figura 2.3 Uso de llaves para denotar composición


$ ,
'
' /
'
'
' descr1 //
/
/
'
'
' /
/
/
'
'
& /
/
.
descr2
“nombre”
'
'
' /
/
/
'
'
'
... /
/
/
'
'
' /
/
/
'
% descr3 /
-

Como mencionamos, se usa la notación de conjuntos, encerrando entre llaves


a los elementos del conjunto; la diferencia es que listamos estos elementos
verticalmente, separados entre sı́ por cambios de renglón. La llave de la
derecha nos estorba, pues nos impide hacer refinamientos de cada uno de los
elementos listados, por lo que en adelante la eliminaremos.

Secuencia: el orden (la secuencia) se denota verticalmente: descr1 se ejecuta


antes que descr2 , y ası́ sucesivamente, en el orden vertical en el que aparecen.
Por ejemplo, si la cena de esta noche consiste de tres platillos, un ceviche, una
sopa, un pescado y un flan de postre, quedarı́a representado de la siguiente
forma:

Figura 2.4 Ejemplo de la descripción de un conjunto


$
'
'
'
'
'
'
ceviche
'
'
'
'
& sopa
cena
'
'
'
'
'
'
pescado
'
'
'
'
% flan

En este caso queda muy claro que el orden en que están listados los platos
2.3 Diseño estructurado 54

de la cena es importante: éste es el orden en que se consumirán y en ningún


otro.

Iteración: la repetición se representa con un paréntesis abajo del “nombre”


donde se indican las reglas de repetición:

Figura 2.5 Iteración en diagramas de Warnier-Orr


$
'
' descr
& descr1
“nombre” 2
pMientras te diganq '
'
%
...
descrn

En el la figura 2.5, la condición de repetición es “mientras te digan”. Eso


querrı́a decir que se ejecutan en orden, descr1 hasta descrn . Al terminar, se
checa si “todavı́a me dicen”. Si es ası́, se regresa a ejecutar completo desde
descr1 hasta descrn , y ası́ sucesivamente hasta que “ya no me digan”, en
cuyo caso sigo adelante.

Siguiendo con el ejemplo de la cena, supongamos ahora que la cena consis-


tirá de tacos y refrescos, tantos como pida el comensal. Suponemos también
que por cada taco que se come toma un sorbo de refresco. El diagrama de
Warnier-Orr se podrı́a mostrar de la siguiente forma:

Figura 2.6 Un diagrama de iteración


$
'
'
'
'
& taco
cena
pMientras tengas hambreq '
'
' refresco
'
%

Condicional:
À Por último, para denotar selección se utiliza el sı́mbolo del o
exclusivo , que aparece entre una pareja de opciones –ver figura 2.7 en la
página opuesta–.
55 El proceso del software

Figura 2.7 Selección en diagramas de Warnier-Orr


$ "
'
'
' dı́gito  1
...
'
'
' ...
'
' À
'
'
' "
'
'
&dı́gito  2 . . .
' ...
“nombre” À
'
'
'
'
'
' ... À
'
'
'
'
'
' "
'
%dı́gito  9 . . .
' ...

Si seguimos con el ejemplo de la cena de tacos y refresco, antes de tomar


refresco podrı́amos preguntar si todavı́a hay refresco, y si ya no hay abrir un
refresco nuevo.

Figura 2.8 Un diagrama de condicional


$
'
'
'
'
'
'
'
'
'
taco
$
'
'
' '
&
'
'
'
'
'
'
hay refresco
' Sı́guele
& %
cena
pMientras tengas hambreq ' À
'
'
' $
'
'
' '
'
' &
'
'
' No hay refresco Abre un refresco
'
'
' '
%
'
'
'
%refresco

Veamos cómo quedarı́an representados los pequeños procesos que dimos arriba
en términos de la notación de Warnier-Orr en las figuras 2.9 y 2.10, donde el
sı́mbolo “Д significa “obtiene el valor de lo que está a la derecha y coloca ese
valor en la variable que está a la izquierda”.
2.3 Diseño estructurado 56

Figura 2.9 Diagramas de Warnier-Orr para secuencia

$
&x Ð 1
'
secuencial x Ð x 2
'
% y Ðx
Figura 2.10 Diagramas de Warnier-Orr para iteración
$
& xÐx 2
Repite
Ðx
p10q % yescribe el valor de x

Por último, el bloque de pseudocódigo que tenemos para la ejecución condi-


cional podrı́a quedar como se ve en la figura 2.11.

Figura 2.11 Diagrama de Warnier-Orr para selección


$
'
' y Ð randp10000q
'
' $
'
'
'
' & z Ð y {x
'
'
'
' x¡1 z Ð z {3
'
'
& '
%escribe el valor de z
selección À
'
'
'
'
'
'
'
'
' #
'
'
' y Ð y {6
%x ¤ 1
'
escribe el valor de y

Cuando tenemos una condicional es frecuente que únicamente estemos veri-


ficando si se da o no una condición, por lo que podemos utilizar la notación de
complemento que se usa en lógica para negar una condición. En lugar de las dos
condiciones x ¡ 1 y x ¤ 1 podemos decir x ¡ 1 y su negación, que es x ¡ 1; o bien
hay refresco y su negación hay refresco, que es equivalente a “no hay refresco”.
Por supuesto que el método de Warnier-Orr nos proporciona herramientas para
llegar a estos esquemas, que se basan, como dijimos antes, en dejar que los datos
nos indiquen las estructuras a través de la jerarquı́a que guardan entre ellos.
Además, utilizando el principio de “divide y vencerás”, decimos que cualquier
problema puede ser dividido en al menos tres partes: prepararse, ejecutarlo y
completar o cerrar. Veamos un ejemplo, el de cambiar un número que viene en
57 El proceso del software

base B a un número en base b. Pediremos que tanto b como B sean menores


o iguales a 10, para tener un dı́gito por posición y usar únicamente los dı́gitos
decimales. Para pasar al número de base B a base b primero lo pasaremos a base
10, y después de base 10 a base b. Siguiendo la notación que acabamos de dar,
todo diagrama de Warnier-Orr empieza con el formato de la figura 2.12.

Figura 2.12 Estado inicial de todo diagrama de Warnier-Orr


$ "
'
'
' .P rincipio
Inicializar
'
'
' Obtener datos
'
& "
N ombre del ...
P roceso
P roblema '' ...
'
'
' "
'
'
% Entregar resultados
.F inal
Atar cabos sueltos

El principio de “ir de lo general a lo particular” indica que intentamos ver el


problema desde un punto de vista general, particularizando únicamente cuando ya
no es posible avanzar sin hacerlo. En este momento, en el problema que estamos
atacando, debemos empezar ya a refinar el proceso. Por último, cuando decimos
que vamos desde los resultados hacia los datos, decimos que el problema lo vamos
resolviendo preocupándonos por cuál es el resultado que debemos producir y cuál
es el paso inmediato anterior para que lo podamos producir.
Para el caso que nos ocupa, cambiar un número de una base a otra, lo podemos
ver en la figura 2.13 en la siguiente página. En el primer momento, todo lo que
sabemos es qué es lo que tenemos de datos (b, B, númeroB ) y qué esperamos de
resultado (el número convertido a base b). Para el principio y final de nuestros
procesos podemos observar que lo último que vamos a hacer es proporcionar o
escribir númerob (correspondiente a númeroB ). Esto corresponde al final de nues-
tro proceso. Sabemos, porque lo vimos al principio del curso, que para pasar un
número en base B a base 10 tenemos que ir calculando los valores de los dı́gitos
en las distintas posiciones y que en este proceso vamos obteniendo el número en
base 10. También sabemos, porque corresponde a nuestros datos, que al principio
de nuestro proceso debemos obtener b, B y el número a convertir. Con esta in-
formación, podemos producir el diagrama inicial que, como ya mencionamos, se
encuentra en la figura 2.13 en la siguiente página.
Progresamos de atrás hacia adelante. Para poder escribir númerob primero,
debemos construirlo. Esto lo hacemos en el bloque inmediato anterior al del final,
que es el que convierte a un número en base 10 a un número en base b. Para hacer
esto, primero tenemos que haber convertido el número en base B a base 10, lo
que hacemos en el bloque anterior. Y antes que nada, al principio, obtenemos los
2.3 Diseño estructurado 58

números que queremos procesar. Esta etapa la podemos observar en el diagrama


de la figura 2.14.

Figura 2.13 Diagrama inicial para pasar un número de base B a base b


$ $
'
'
' '
&Obtener b
'
'
'
'
'
'
.Principio
'
%
Obtener B
'
'
Cambio de '
Obtener númeroB
&Cambiar a !
base B a ...
base b ''
'
base 10
!
'
'
'
Cambiar a
...
'
'
'
base b "
'
'
'
% .Final Escribir númerob

Figura 2.14 Diagrama de Warnier-Orr para procesar cada una de las partes
del proceso $ $ !
'
' '
'
'
'
'
' '
&
Obtener b
!
...
'
'
'
'
'
'
.Principio Obtener B
' ...
'
' '
' !
'
' '
%Obtener númeroB . . .
'
'
'
'
' $ !
'
'
' '
'
'
' '
'
'
Principio ...
'
' '
& Calcular dı́gito #
'
'
'
'
'
Cambiar a (mientras
Cambio de '
...
& base 10 '
' '
'
' número B ¡ 0 q !
base B a '
'
%Final
' ...
base b ''
' $ !
'
'
'
'
' '
'
' Principio
'
'
' '
' #
...
'
' '
& Calcular dı́gito
'
'
' Cambiar a ...
'
' '
(mientras
' número10 ¡ 0q
base b
'
' '
'
' !
...
'
'
' '
'
'
' %Final ...
'
'
'
'
' "
'
'
%.Final Escribir número
b
59 El proceso del software

Nos falta desarrollar los bloques que corresponden a verificar que la información
dada por el usuario es correcta –que las bases sean menores a 10–, pasar de base
B a base 10 y, por último, pasar de base 10 a base b.
Verificar que los números dados sean correctos es un proceso fácil. Simplemente
preguntamos si los números están en rangos. Si es ası́, seguimos adelante y si no,
abortamos el proceso con un mensaje adecuado.
Para pasar de base B a base 10 vamos obteniendo el último dı́gito y mul-
tiplicándolo por la potencia correspondiente. Empezamos con B 0  1 y en cada
vuelta (iteración) multiplicamos la potencia por la base, para ir obteniendo las po-
tencias correspondientes. Para pasar de base 10 a base b, dividimos sucesivamente
el cociente entero entre la base, y vamos pegando, por la izquierda, los dı́gitos
que obtengamos como el residuo. El diagrama correspondiente a estos bloques lo
mostramos en la figura 2.15 (en dos partes).

Figura 2.15 Diagrama para pasar un número de base 10 a otra base (1/2)

$ $ $ "
'
' '
' '
'
' b ¡ 10
Mensaje: no se puede
'
' '
'
' & aborta
'
'
' '
'
'
'
'
'
'
'
'
Obtener b
'
' ` !
'
'
' '
' '
%b ¡ 10 ∅
'
' '
'
'
'
'
' & $ "
'
' '
' B ¡ 10
Principio ' Mensaje: no se puede
'
'
' '
' '
&
'
' '
'
'
aborta
'
'
' '
' Obtener B ` !
'
' '
'
' '
'
'
'
'
' '
' %B ¡ 10 ∅
'
' '
'
'
'
'
' '
%Obtener númeroB
'
'
' #
'
' $
'
'
' '
' iniciar potencia  1
'
' '
' Principio
'
' '
'
' iniciar número10  0
'
'
' '
' $
'
' '
'
'
' '
'
' '
'
' dı́gito Ð númeroB mod 10
'
' ' '
'
'
' &Calcular dı́gito &número10 Ð número10
'
' '
'
'
'
'
Cambiar a (mientras pdı́gito  potenciaq
'
' base 10 '
' ¡ 0 q '
'
' potencia  potencia  B;
'
'
' '
'
número B
'
'
' '
' % númeroB  númeroB  10;
Cambio de& '
'
base B a '
'
' $
base b '' '
'
' &
'
'
' '
'
'
' '
%Final %Escribir valor de número10
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
'
$
'
'
'
'
'
2.3 Diseño estructurado ' '
' 60
'
'
'
'
'
Figura 2.15 Diagrama ' '
'
para pasar un número de una base a otra
' (2/2)
'
'
'
'
'
' $ #
'
'
' '
' númerob Ð 0
'
' '
' Principio
' '
' potencia Ð 1
Cambio de'& '
'
'
'
' $
base B a
' '
&Calcular dı́gito ' ' residuo  número mod b
base b ''Cambiar a &número Ð pegar 10
'
'
'
' base b '  númerob
b
(mientras
'
' '
'
' número10 ¡ 0q ' ' residuo
'
' ' %
'
' '
'
' número10 Ð número10  b
'
'
' '
'
'
' '
' !
'
' %Final
'
'
'

'
' !
'
'
'.Final Escribir númerob
'
'
%

Cuando alguno de los procesos no tiene que hacer nada simplemente le ponemos
un signo de conjunto vacı́o (∅). Varios de los procesos de P rincipio y F inal no
tienen encomendada ninguna tarea, por lo que aparecen vacı́os. Por ejemplo, el
proceso de F inal de cambiar a base b podrı́a escribir el resultado, pero esto ya lo
hace el F inal de todo el proceso; en este caso, no hacemos nada en el F inal del
cambio a base b.
Una vez completo el diagrama de Warnier de un problema, el programa está da-
do realmente por los enunciados en la jerarquı́a menor (éstos son realmente los
enunciados ejecutables), respetando las estructuras de control de repetición y con-
dicionales. Conforme vayamos avanzando en el curso, trataremos de adquirir expe-
riencia en el diseño estructurado. No hay que ignorar el hecho de que la intuición
y la experiencia juegan un papel importante en esta etapa. Con esto queda ter-
minado el algoritmo (y los diagramas correspondientes) para pasar un número de
una base a otra.
Resumiendo y haciendo el sı́mil con arquitectura, una clase corresponde a los
planos de una casa mientras que cada casa que se construye de acuerdo a esos
planos corresponde a una instancia u objeto de esa clase. Para poder determinar
el cómo cumple con sus responsabilidades cada objeto, cómo elaborar los métodos
(no siempre tenemos presentes a todos los tipos de métodos), debemos recurrir al
diseño estructurado. Hablaremos más de este tema más adelante en el curso El
método de Warnier-Orr no proporciona mecanismos para analizar problemas de
naturaleza recursiva. Este tipo de soluciones estará dado por las fórmulas mismas,
o la descripción de la solución.
61 El proceso del software

Procedemos ahora a incursionar en otros problemas, donde utilizaremos la


metodologı́a que acabamos de describir.

Ejercicios

2.1.- ¿Cuáles son las diferencias principales entre el análisis orientado a objetos
y el análisis estructurado?
2.2.- Tenemos el siguiente relato:
La tienda de abarrotes vende productos que tienen un precio fijo
por unidad y productos que se venden por kilo y tienen precio
por peso. También preparan tortas de jamón, queso de puerco y
salchicha, cada una con un precio distinto pero todas se preparan
básicamente igual. Cuando alguien compra algo, primero paga y
después se le proporciona lo que pidió. Si lo que compra es una
torta, ésta se prepara al momento, mientras que los otros productos
simplemente se toman de los estantes y se entregan. Tiene una
caja registradora donde se cobra y un lugar donde se entrega lo
que se pidió.

(a) Subraya los sustantivos (objetos) y tacha los verbos (métodos).


(b) Clasifica a los objetos en clases.
(c) Asigna a cada clase sus responsabilidades (qué le toca hacer a cada
quien).
(d) Haz un diagrama de Warnier-Orr del proceso de compra de un producto.

2.3.- Tenemos el siguiente relato:


La facultad de Ciencias ofrece las carreras de Actuarı́a, Biologı́a,
Ciencias de la Computación, Ciencias de la Tierra, Fı́sica, Ma-
temáticas. Todas, excepto por la de Fı́sica, se cursan en 8 semes-
tres; la de Fı́sica en 9. Para obtener el tı́tulo el alumno tienen que
cursar el número de semestres de la carrera y además presentar
un trabajo terminal.

(a) Subraya los sustantivos (objetos) y tacha los verbos (métodos).


2. Ejercicios 62

(b) Clasifica a los objetos en clases.


(c) Asigna a cada clase sus responsabilidades (qué le toca hacer a cada
quien).
(d) Haz un diagrama de Warnier-Orr del proceso de estudiar en la Facultad
de Ciencias, incluyendo únicamente lo que se menciona en el texto.

2.4.- Haz el diagrama de Warnier que corresponde a pasar un número de base 16


a base 2.

2.5.- Haz el diagrama de Warnier que corresponde a resolver una ecuación de


segundo grado de la forma ax2 bx c  0 (Nota: toma en cuenta que a  0
o b2  4ac   0).

2.6.- Haz el diagrama de Warnier que corresponde a resolver un sistema de ecua-


ciones con dos incógnitas:

ax by  c
dx ey  f

2.7.- Si pensamos que para obtener el factorial de un número n lo que hacemos


es ir multiplicando un resultado parcial, que empieza en 1 por cada uno de
los enteros entre 1 y n, decimos que el proceso es iterativo:
r e s u l t P a r c i a l es 1.
Repite para j desde 2 hasta n :
resultParcialÐ resultaParcial  n.
Escribe el resultado

Haz el diagrama de Warnier que corresponde a este pseudocódigo.

2.8.- Supongamos que queremos obtener la suma de los cuadrados de los números
impares entre el 1 y el 17. Haz el diagrama de Warnier que corresponde a
este proceso.

2.9.- Deseas anotar los nombres de todos los estudiantes que están inscritos en
más de seis materias en la Facultad de Ciencias. Haz el diagrama de Warnier
que avise a los estudiantes que pasen a anotarse y el proceso de anotarse.

2.10.- Haz un diagrama de Warnier de los pasos que tienes que seguir para des-
pertarte en la mañana y salir de tu casa.
Clases y objetos
3
3.1 Tarjetas de responsabilidades

Para ilustrar de mejor manera el proceso de análisis y diseño procederemos


a trabajar con un ejemplo concreto: el diseño de un reloj digital. Este ejemplo,
aunque sencillo, nos va a permitir seguirlo hasta su implementación. Los pasos
para ello son, como lo mencionamos en secciones anteriores, hacer diagramas o
esquemas para cada una de las etapas de definición, que volvemos a listar:
Acción A partir de: Produce:
1. Encontrar las Una descripción o Una lista de objetos y un esquema para
clases y los especificación del cada clase.
objetos. problema.
2. Determinar las La lista de objetos. Esquema de clases con lista de funcio-
responsabilida- La especificación nes miembro o métodos, clasificados en
des. del problema. públicos y privados, y con un breve des-
cripción de qué se supone que hace cada
una.
3. Determinar la La lista de Adiciona al esquema de responsabilida-
colaboración responsabilidades. des, para cada función o método, quién
entre objetos. La lista de objetos. la puede llamar y a quién le va a respon-
der.
3.1 Tarjetas de responsabilidades 64

Acción A partir de: Produce:


4. Determinar la El esquema de El esquema revisado para que coincida
accesibilidad de colaboración y con las reglas que se dieron para accesi-
las funciones y responsabilidades. bilidad.
datos.
Pasemos a analizar nuestro problema.

Paso 1: Descripción del problema


Descripción del problema:
Un reloj digital consiste de una carátula de despliegue, con dos manecillas,
una para horas y una para minutos. Cada manecilla tendrá un valor entre 0
y un lı́mite superior prefijado (en el caso de los minutos es 60, mientras que
para las horas es 12). El usuario del programa debe ser capaz de construir el
reloj inicializando el valor de cada manecilla a las 0 horas con 0 minutos, o
bien, a un valor arbitrario (que podrı́a ser el reloj de la máquina). El usuario
debe ser capaz también de incrementar el reloj incrementando la manecilla
de los minutos y algunas veces también la manecilla de las horas. El usuario
deberá ser capaz de establecer el reloj en un cierto valor, estableciendo el
valor de cada una de las manecillas. Finalmente, el usuario puede pedirle
al reloj que muestre su valor mostrando la posición de cada una de las
manecillas.

Del párrafo anterior, podemos distinguir los siguientes objetos:


reloj valor de horas
manecilla de minutos valor de minutos
manecilla de horas valor del reloj
lı́mites
Podemos ver que el objeto reloj, “posee” dos objetos que corresponde cada
uno de ellos a una manecilla. Cada manecilla posee un objeto valor y un objeto
lı́mite. El valor concreto de cada manecilla es suficientemente simple como para
que se use un entero para representarlo, lo mismo que el lı́mite; excepto que este
último debe ser constante porque una vez que se fije para cada manecilla, ya no
deberá cambiar en esa manecilla. Las horas y los minutos, con su valor y lı́mite
correspondientes, pertenecen a la misma clase. Podemos entonces mostrar nuestra
estructura en la figura 3.1.
65 Clases y objetos

Figura 3.1 Estructura de las clases de un reloj digital


Manecilla
Datos: valor
lı́mite
Funciones: constructores:
Poner el lı́mite e iniciar tiempo
incrementa
pon valor
muestra
Reloj
Datos: horas
Una manecilla con lı́mite 12
minutos
Una manecilla con lı́mite 60
Funciones: constructores
incrementa
pon valor
muestra

En la clase Manecilla tenemos dos variables, valor y LIMITE, que no queremos


puedan ser manipuladas directamente, sin controlar que se mantenga en los lı́mites
establecidos, por lo que van a tener acceso privado. Por ello es conveniente agregar
dos responsabilidades (métodos) a esta clase, getValor y getLIMITE, para que se
le pueda pedir a los objetos de la clase, en caso necesario, que diga cuánto valen1 .

Paso 2: Elaboración de tarjetas de responsabilidades


En este punto podemos hacer una distribución de los objetos en clases y un
esquema de las clases determinadas que presente qué es lo que tiene que contener
y tener cada clase, anotándolo en una tarjeta, como se ve en la figura 3.2 en la
siguiente página.
1
Usaremos la palabra get en estos casos, en lugar del término en español da, ya que en Java
existe la convención de que los métodos de acceso a los atributos de una clase sean de la forma get
seguidos del identificador del atributo empezado con mayúscula. Similarmente en los métodos
de modificación o actualización de los valores de un atributo la convención es usar set seguido
del nombre del atributo escrito con mayúscula.
3.1 Tarjetas de responsabilidades 66

Figura 3.2 Tarjetas de clasificación y acceso

Clase: Reloj Clase: Manecilla


P constructores P constructores
ú incrementa ú incrementa
b setValor b setValor
l muestra l muestra
i i getValor
c c getLı́mite
o o
P P
r horas r valor
i i
v minutos v lı́mite
a a
d d
o o

Si completamos estos esquemas con las responsabilidades de cada quien, van


a quedar como se muestra en la figura 3.3 para la clase Reloj y en la figura 3.4 en
la página opuesta para la clase Manecilla.

Figura 3.3 Tarjeta de responsabilidades de la clase Reloj

Clase: Reloj (responsabilidades)


P constructor Inicializa el reloj a una hora dada.
ú Para ello, debe construir las manecillas.
b incrementa Incrementa el reloj en un minuto
l
i setValor Pone un valor arbitrario en el reloj
c
o muestra Muestra el reloj
P horas Registra el valor en horas
r
i minutos Registra el valor en minutos
v
a
d
o
67 Clases y objetos

Figura 3.4 Tarjeta de responsabilidades para la clase Manecilla

Clase: Manecilla (responsabilidades)


P constructor Establece el lı́mite de la manecilla y da valor
ú inicial a la manecilla
b incrementa Incrementa el valor y avisa si alcanzó el lı́mite
l setValor Pone valor a la manecilla
i muestra Pone en una cadena el valor
c getValor Dice el valor que tiene
o getLIMITE Regresa el valor del lı́mite
P valor Tiene la información de la manecilla
r LIMITE Tiene la información respecto al lı́mite
i
v
a
d
o

Paso 3: Determinar colaboración


El tercer paso nos dice que determinemos la colaboración entre los objetos. En
una primera instancia podemos ver que un objeto de la clase Reloj puede pedirle
a cada uno de los objetos de la clase Manecilla que haga su parte: construirse,
mostrar su valor, incrementarse. Podemos afinar nuestra descripción de cada una
de las clases, describiendo de manera breve en qué consiste cada método o función
propia y definiendo la colaboración (quién inicia las acciones, o quién le solicita a
quién, que llamamos el cliente):

Clase: Cliente: Descripción:


Manecilla
Datos valor el valor actual de la manecilla, en el
rango 0..lı́mite  1.
lı́mite el valor en el que el reloj “da la vuel-
ta” o se vuelve a poner en ceros
Métodos Constructor Reloj Pone el valor de la manecilla en ceros
y establece el lı́mite
incrementa Reloj Suma 1 al valor y lo regresa a cero
si es necesario. Avisa si lo regresó a
cero.
3.1 Tarjetas de responsabilidades 68

Clase: Cliente: Descripción:


setValor Reloj Pone el valor
muestra Reloj Muestra el valor que tiene la
manecilla
Reloj
Datos horas Una manecilla con lı́mite 12
minutos Una manecilla con lı́mite 60
Métodos Constructor usuario Manda un mensaje a ambas maneci-
llas instalando sus lı́mites respectivos
incrementa usuario Incrementa la manecilla de minutos,
y si es necesario la de horas
setValor usuario Establece el tiempo en el reloj y pa-
ra ello lo establece en las manecillas
horas y minutos
muestra usuario Pide a las manecillas que se “acomo-
den” en una cadena

En forma esquemática las tarjetas quedan como se muestran en las figuras 3.5 a
continuación y 3.6 en la página opuesta.

Figura 3.5 Tarjeta de colaboraciones de la clase Manecilla

Clase: Manecilla (colaboración)


P constructor El Reloj a la manecilla
ú incrementa El Reloj a la manecilla
b setValor El Reloj a la manecilla
l
i muestra El Reloj a la manecilla
c getValor El Reloj a la manecilla
o getLIMITE El Reloj a la manecilla
P valor
r lı́mite
i
v
a
d
o
69 Clases y objetos

Figura 3.6 Tarjeta de colaboraciones de la clase Reloj

Clase: Reloj (colaboración)


P constructor El usuario al Reloj
ú incrementa El usuario al Reloj
b
l setValor El usuario al Reloj o Reloj a sı́ mismo
i muestra El usuario al Reloj
c
o
P horas
r minutos
i
v
a
d
o

Tenemos ya completo el paso de análisis y diseño, ya que tenemos las tarjetas


de responsabilidades completas. Pasemos ahora al siguiente paso en la elaboración
de un programa, que consiste en la instrumentación del diseño para ser ejecutado
en una computadora.
Si bien el diseño orientado a objetos no es un concepto reciente (aparece alre-
dedor de 1972), lo que sı́ es más reciente es la popularidad de las herramientas que
facilitan la transición de un modelo orientado a objetos a un programa orientado
a objetos. El primer lenguaje que maneja este concepto es Simula (hermanito de
Algol 60), aunque su popularidad nunca se generalizó.
Al mismo tiempo que Wirth diseñaba el lenguaje Pascal (una herramienta
de programación para el diseño estructurado), se diseñó Smalltalk, un lenguaje
orientado a objetos, de uso cada vez más generalizado hoy en dı́a. También se han
hecho muchas extensiones a lenguajes “estructurados” para proveerlos de la capa-
cidad de manejar objetos. Entre ellos tenemos Objective Pascal, C++, Objective
C, Modula 3, Ada. Muchos de los abogados de la programación orientada a obje-
tos consideran a este tipo de extensiones como “sucias”, pues en muchas ocasiones
mezclan conceptos, o cargan con problemas que se derivan de tratar de mantener la
relación con sus lenguajes originales. Hemos seleccionado Java como herramienta
de instrumentación pues contamos con amplia bibliografı́a al respecto, aceptación
generalizada fuera de los ambientes académicos, acceso a muy diversas versiones
de la herramienta. Estamos conscientes, sin embargo, de que Java es un lenguaje
sumamente extenso, por lo que no pretendemos agotarlo en este curso.
3.2 Programación en Java 70

3.2 Programación en Java


En todo lenguaje de programación hay involucrados tres aspectos, relativos a
los enunciados escritos en ese lenguaje:
Sintaxis: Se refiere a la forma que tiene que tomar el enunciado. Cada lenguaje
tiene sus propias reglas y corresponderı́a a la gramática para un lenguaje
natural. Utilizamos para describir la sintaxis lo que se conoce como BNF
extendido.
Semántica: Se refiere de alguna manera al significado del enunciado. General-
mente el significado corresponde a la manera cómo se ejecuta el enunciado,
una vez traducido a lenguaje de máquina (en el caso de Java a bytecode).
Usaremos lenguaje natural y predicados para describir este aspecto.
Pragmática: Se refiere a restricciones o caracterı́sticas dadas por la computadora
o la implementación del lenguaje. Por ejemplo, un entero en Java tiene un
lı́mite superior e inferior, que no corresponde a lo que entendemos como
entero. Este lı́mite es impuesto por la implementación del lenguaje o de la
computadora en la que se van a ejecutar los programas. Usaremos lenguaje
natural para hablar de la pragmática de un enunciado.
Hablemos un poco de BNF extendido, donde cada enunciado se muestra como si
fuera una fórmula:
xtérmino a definiry :: xexpresión regulary
En esta notación del lado izquierdo de “::=” aparece lo que serı́a un tipo de ele-
mentos, lo que vamos a definir, como por ejemplo acceso, encerrado entre x y y para
distinguir al conjunto de alguno de sus representantes. El “::=” se lee “se define
como”; del lado derecho se encuentra xexpresión regulary, que puede contener a su
vez conjuntos o elementos del lenguaje. Una expresión regular es una sucesión de
sı́mbolos terminales y no terminales (como en cualquier gramática), pero donde
extendemos la gramática de la siguiente manera: usamos paréntesis para agrupar
–cuando queramos que aparezca un paréntesis tal cual lo marcaremos con negri-
tas–; el sı́mbolo “ | ” para denotar opciones; el sı́mbolo “ * ” para denotar que el
grupo anterior se puede repetir cero o más veces; y “+” para denotar que el grupo
anterior se puede repetir una o más veces. A los elementos del lenguaje (represen-
tantes de los conjuntos, sı́mbolos terminales) los escribimos con negritas, tal como
deben aparecer en el archivo fuente. Conforme vayamos avanzando quedará más
claro el uso de BNF.
Cuando describamos un recurso del lenguaje, sea éste un enunciado o la manera
de organizar a éstos, hablaremos al menos de los dos primeros aspectos; el tercero
lo trataremos en aquellos casos en que tenga sentido.
71 Clases y objetos

Como Java es un lenguaje orientado a objetos, la modularidad de los programas


en Java se da a través de clases. Una clase es, como ya dijimos, una plantilla para
la construcción de objetos, una lista de servicios que los objetos de la clase van a
poder realizar y un conjunto de atributos (campos, variables) que determinan el
estado de cada objeto de e
Otro elemento que utiliza Java para construir sus aplicaciones es la interfaz.
Una interfaz en Java describe a un grupo de servicios, en términos de lo que los
objetos de las clases que la implementen saben hacer, esto es, lista únicamente
los servicios que la clase en cuestión va a dar, utilizando qué datos de entrada y
proporcionando qué resultados; no se involucra en lo absoluto con el cómo llevar
a cabo esos servicios. Una interfaz corresponde a un contrato. Posteriormente
podemos construir una o más clases capaces de cumplir con ese contrato, donde
se describirán las (distintas) formas en que se van a llevar a cabo los servicios.
A esto último le llamamos implementar a la interfaz. Trataremos de trabajar
siempre a través de interfaces, pues nos dan un nivel de abstracción más alto que
el que nos dan las clases -=-nos dicen el qué, no el cómo–. Todo lo relativo a
una aplicación (los archivos de código) se agrupa en un paquete –que corresponde
a un subdirectorio– al que le asignamos un nombre.
Decimos que declaramos una interfaz o una clase cuando escribimos la plantilla
en un archivo, al que denominamos archivo fuente. Se acostumbra, aunque no es
obligatorio, que se coloque una clase o interfaz por archivo2 para tener fácilmente
identificable el archivo fuente de la misma. El nombre que se dé al archivo, en este
caso, debe coincidir con el nombre de la clase o interfaz. Por ejemplo, la clase Reloj
deberá estar en un archivo que se llame Reloj.java; de manera similar, la interfaz
ServiciosReloj deberá estar en un archivo que se llame ServiciosReloj.java.

3.2.1. Declaraciones en Java

Lo primero que haremos en Java es, entonces, la definición (declaración) de


una interfaz. La sintaxis para ello se puede ver en la figura 3.7 en la siguiente
página.
Las palabras que aparecen en negritas tienen que aparecer tal cual. Ése es
el caso de interface y el ; que aparece al final de cada xencabezado de métodoy.
Los que aparecen entre x y y deben ser proporcionados por el programador, si-
guiendo ciertas reglas para ello. En Java el punto y coma (;) se usa para terminar

2
La única restricción real para que haya más de una clase en un archivo es en términos de
identificarla, pues no habrá un archivo fuente con el nombre de la clase. Pero sı́ habrá el archivo
correspondiente al bytecode de la clase (nombre.class).
3.2 Programación en Java 72

enunciados, como las declaraciones (los encabezados de un método juegan el papel


de una declaración). Por ejemplo, la sintaxis para el xaccesoy es como se ve en la
figura 3.8, mientras que un xidentificadory es cualquier sucesión de letras, dı́gitos
y carácter de subrayado, que empiece con letra o subrayado.
Figura 3.7 Encabezado de una interfaz
Sintaxis:
x declaración de interfazy ::= xaccesoy interface xidentificadory {
xencabezado de métodoy;
(xencabezado de métodoy;)*
}
Semántica:
Se declara una interfaz en un archivo. El nombre del archivo debe tener co-
mo extensión .java y coincide con el nombre que se le dé a la interfaz. Una
interfaz, en general, no tiene declaraciones de atributos, sino únicamente
de métodos, de los cuáles únicamente se da el encabezado. Los encabezados
de los distintos métodos se separan entre sı́ por un ; (punto y coma). El
que únicamente contenga encabezados se debe a que una interfaz no dice
cómo se hacen las cosas, sino únicamente cuáles cosas sabe hacer.

Figura 3.8 Sintaxis para el xaccesoy


Sintaxis:
xacceso y ::= public | private | protected | ∅
Semántica:
El acceso a una clase determina quién la puede usar:
public La puede usar todo mundo.
private No tiene sentido para una clase que corresponde a un nombre de
archivo, ya que delimita a usar la clase a la misma clase: no se conoce
fuera de la clase.
protected Sólo la pueden ver las clases que extienden a ésta. No tiene
sentido para clases.
∅ Cuando no aparece ningún tipo de acceso, éste es de paquete (package)
y es el valor por omisión. Sólo la pueden ver clases que se encuentren
declaradas en el mismo subdirectorio (paquete). No existe la palabra
reservada package para denominar este tipo de acceso.
En el caso de las interfaces, el acceso sólo puede ser de paquete o público, ya
que el concepto de interfaz tiene que ver con anunciar servicios disponibles.

Siguiendo la notación de BNF extendido, el enunciado de Java para denotar a


un elemento del conjunto xidentificadory quedarı́a como se ve en la figura 3.9.
73 Clases y objetos

Figura 3.9 Reglas para la formación de un xidentificadory


Sintaxis:
xidentificador y ::= (xletra y | )(xletray | xdı́gitoy | )*
Semántica:
Los identificadores deben ser nemónicos, esto es, que su nombre ayude a la
memoria para recordar qué es lo que representan. No pueden tener blancos
insertados. Algunas reglas no obligatorias (aunque exigidas en este curso y
consideradas de buena educación en la comunidad de Java) son:
Clases: Empiezan con mayúscula y consiste de una palabra descriptiva,
como Reloj, Manecilla.
Métodos: Empiezan con minúsculas y se componen de un verbo –da, cal-
cula, mueve, copia– seguido de uno o más sustantivos. Cada uno de
los sustantivos empieza con mayúscula.
Variables: Nombres sugerentes con minúsculas.
Constantes: Nombres sugerentes con mayúsculas.
Hay que notar que en Java los identificadores pueden tener tantos carac-
teres como se desee. El lenguaje, además, distingue entre mayúsculas y
minúsculas –no es lo mismo carta que Carta–.
Una interfaz puede servir de contrato para más de una clase (que se llamen
distinto). Es la clase la que tiene que indicar si es que va a cumplir con algún
contrato, indicando que va a implementar a cierta interfaz.
El acceso a los métodos de una interfaz es siempre público o de paquete. Esto
se debe a que una interfaz anuncia los servicios que da, por lo que no tendrı́a
sentido que los anunciara sin que estuvieran disponibles.
Siempre es conveniente poder escribir comentarios en los programas, para que
nos recuerden en qué estábamos pensando al escribir el código. Tenemos tres tipos
de comentarios:
Empiezan con // y terminan al final de la lı́nea.
Todo lo que se escriba entre /* y */. Puede empezar en cualquier lado y
terminar en cualquier otro. Funcionan como separador.
Todo lo que se escriba entre /** y */. Estos comentarios son para JavaDoc,
de tal manera que nuestros comentarios contribuyan a la documentación del
programa3 .
Utilizaremos de manera preponderante los comentarios hechos para JavaDoc,
en particular para documentar interfaces, clases y métodos. Los comentarios deben
3
Si se está utilizando el editor de Emacs, se puede agregar automáticamente la documentación
de JavaDoc tecleando C-c C-v j o, equivalentemente, eligiendo con el ratón JDE ¡ Documentation
¡ Add.
3.2 Programación en Java 74

tener en el primer renglón únicamente /∗∗, y cada uno de los renglones subsecuen-
tes, menos el último, deberán empezar con un asterisco. En el último renglón
aparecerá únicamente ∗/. A partir del segundo renglón deberá aparecer una des-
cripción breve del objetivo de la clase o interfaz.
En el caso de los comentarios de las clases e interfaces, tenemos entre otros
un campo, @author, que nos indica quién es el autor de esa clase o interfaz, y un
campo @version para anotar ahı́ las modificaciones que vayamos realizando.
La interfaz para nuestro reloj deberı́a anunciar a los servicios que listamos para
el reloj en la figura 3.5 en la página 68 –excepto por el constructor–, y la interfaz
para la clase Manecilla debe listar los servicios que listamos en la figura 3.6 en la
página 69 –también excluyendo al constructor–. Pospondremos por el momento la
codificación de los encabezados de los métodos hasta que veamos este tema con
más detalle. La codificación del encabezado de las interfaces para Reloj y Manecilla
se encuentran en los listados 3.1 y 3.2.

Código 3.1 Encabezado de la interfaz para Reloj ServiciosReloj

10 package R e l o j ;
20 /∗ ∗
30 ∗ I n t e r f a c e <code>S e r v i c i o s R e l o j </code> d e s c r i b e l o s s e r v i c i o s
40 ∗ que da un r e l o j d i g i t a l .
50 ∗
60 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
70 ∗ @ v e r s i o n 1 . 0
80 ∗/
90 p u b l i c i n t e r f a c e S e r v i c i o s R e l o j {
/∗ L i s t a de métodos a d e s c r i b i r ∗/
300 } // S e r v i c i o s R e l o j

Código 3.2 Encabezado de la interfaz para Manecilla ServiciosManecilla

100 package R e l o j ;
200 /∗ ∗
300 ∗ I n t e r f a c e <code>S e r v i c i o s M a n e c i l l a </code> d e s c r i b e l o s s e r v i c i o s
400 ∗ que da una m a n e c i l l a de un r e l o j d i g i t a l .
500 ∗
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
700 ∗ @ v e r s i o n 1 . 0
800 ∗/
900 p u b l i c i n t e r f a c e S e r v i c i o s M a n e c i l l a {
/∗ Métodos a i m p l e m e n t a r en l a c l a s e M a n e c i l l a ∗/
4800 } // S e r v i c i o s M a n e c i l l a
75 Clases y objetos

Notarán que en los comentarios de JavaDoc aparecen los nombres de las clases
entre las cadenas <code> </code>. Esta notación corresponde a xml y es para
construir las páginas de web donde se describe a cada clase.
Veamos en la figura 3.10 la sintaxis y semántica del encabezado de una clase.
Ésta es una descripción parcial, ya que por el momento no tiene sentido ver la
definición completa.

Figura 3.10 Encabezado de una clase


Sintaxis:
xdeclaración de clasey ::=
xaccesoy class xidentificadory
(∅ |p implements xidentificadory
p,xidentificadory)*)
(∅ | extends xidentificadory) {
xdeclaracionesy
(∅ | xmétodo mainy)
}
Semántica:
Se declara una clase en un archivo. El nombre del archivo debe tener como
extensión .java y, en general, coincide con el nombre que se le dé a la
clase. Una clase debe tener xdeclaracionesy y puede o no tener xmétodo
mainy. La clase puede o no adoptar una interfaz para implementar. Si lo
hace, lo indica mediante la frase implements e indicando a cuál o cuáles
interfaces implementa. Las xdeclaracionesy corresponden a los ingredientes
(variables, atributos, referencias) y a los métodos que vamos a utilizar. El
xmétodo mainy, que en realidad también forma parte de las declaraciones,
se usa para poder invocar a la clase desde el sistema operativo. Si la clase va
a ser invocada desde otras clases, no tiene sentido que tenga este método.
Sin embargo, muchas veces para probar que la clase funciona se le escribe
un método main, donde se invocan a los métodos declarados. En Java todo
identificador tiene que estar declarado para poder ser usado.

Cuando un archivo que contiene interfaces o clases se compila bien aparecerá en


el subdirectorio correspondiente un archivo con el nombre de cada una de las clases
o interfaces, pero con el sufijo .class, que es la clase o interfaz pero en bytecode.
Éste es el archivo que va a ser interpretado por la Máquina Virtual de Java y el
que puede ser ejecutado o invocado.
Vamos codificando lo que ya sabemos cómo. Tenemos dos interfaces, Servi-
3.2 Programación en Java 76

ciosReloj y ServiciosManecilla, para los que tenemos que definir los servicios que
cada una de ellas va a “contratar”. Regresamos a las tarjetas de responsabilidades
donde los servicios corresponden a los verbos y van a ser implementados a través
de métodos. Sabemos que hay cinco tipos posibles de métodos:
(a) Constructores. Son los que hacen que los objetos de esa clase existan.
(b) De acceso. Son los que permiten conocer el estado del objeto.
(c) Mutantes o de modificación. Son los que permiten modificar el estado del
objeto.
(d) De implementación. Son los que dan los servicios que se requieren del ob-
jeto.
(e) Auxiliares. Los que requiere el objeto para dar sus servicios de manera ade-
cuada.
Como los métodos involucrados en la interfaz deben ser públicos o de paquete,
sólo los de tipo b, c y d van a aparecer en la definición de la interfaz correspondien-
te. Asimismo, tampoco se pone en la interfaz a los métodos constructores, pues la
interfaz no define ni es capaz de construir objetos. Pospondremos la descripción
de los métodos de tipo a y e para cuando revisemos con detalle la definición de
clases.
Lo que aparece en la interfaz es únicamente el encabezado de los métodos que
van a ser de acceso público o de paquete. Los métodos de actualización o de imple-
mentación pueden recibir como entrada datos a los que llamamos parámetros. Los
parámetros también se pueden usar para manipulación o para dejar allı́ informa-
ción. Un parámetro es, simplemente, una marca de lugar para que ahı́ se coloquen
datos que el método pueda usar y que pueda reconocer usando el nombre dado
en la lista. Si regresamos al sı́mil de una obra de teatro, podemos pensar que los
parámetros corresponden a la lista de los personajes que viene, adicionalmente,
con una descripción de si el personaje es alto, viejo, mujer, etc. (porque el puro
nombre no me indica a qué clase de actor contratar para ese papel). El guión
viene después en términos de estos personajes: “Hamlet dice o hace”. El guión
nunca dice quién va a hacer el papel de Hamlet; eso se hace cuando se “monta”
la obra. De manera similar con los parámetros, no es sino hasta que se invoca al
método que hay que pasar valores concretos. A la lista de parámetros se les llama
también parámetros formales. Cuando se invoque el método deberán aparecer los
“actores” que van a actuar en lugar de cada parámetro. A estos les llamamos los
argumentos o parámetros reales. Daremos la sintaxis de los parámetros cuando
aparezcan en alguna definición sintáctica.
En el encabezado de un método cualquiera se localiza lo que se conoce como
la firma del método, que consiste del nombre del método y una lista de los tipos
77 Clases y objetos

de los parámetros –respetando el orden–. Además de la firma, en el método se


marca de alguna manera el tipo de método de que se trata. Esto lo revisaremos
conforme veamos los distintos tipos de métodos.
Para documentar los distintos métodos de nuestra aplicación utilizaremos tam-
bién JavaDoc, donde cada comentario empieza y termina como ya mencionamos.
En el caso de los métodos, en el segundo renglón deberá aparecer una descripción
corta del objetivo del método (que puede ocupar más de un renglón) que debe
terminar con un punto. Después del punto se puede dar una explicación más am-
plia. A continuación deberá aparecer la descripción de los parámetros, cada uno
en al menos un renglón precedido por @param y el nombre del parámetro, con una
breve explicación del papel que juega en el método. Finalmente se procederá a
informar del valor que regresa el método, precedido de @returns y que consiste de
una breve explicación de qué es lo que calcula o modifica el método.

Métodos de acceso
Los métodos de acceso los tenemos para que nos informen del estado de un
objeto, esto es, del valor de alguno de los atributos del objeto. Por ello, la firma
del método debe tener información respecto al tipo del atributo que queremos
observar. La sintaxis se puede ver en la figura 3.11, donde las definiciones de
xtipoy, xaccesoy e xidentificadory son como se dieron antes.
Figura 3.11 Encabezado para los métodos de acceso
Sintaxis:
x encabezado de método de acceso y ::=
xaccesoy xtipoy xidentificadory ( xParámetros y)
Semántica:
La declaración de un método de acceso consiste del tipo de valor que desea-
mos ver, ya que nos va a “regresar” un valor de ese tipo –es el resultado
o salida que proporciona el método–, seguido de la firma del método, que
incluye a los xParámetrosy –que corresponden a la entrada que le vamos a
proporcionar al método para que trabaje–. El identificador del método es
arbitrario, pero se recomienda algo del estilo “getAtributo”, que consista de
un verbo que indica lo que se va a hacer, y un sustantivo que corresponde
al identificador que le dimos al atributo.

Los tipos que se manejan en Java pueden ser primitivos o definidos por el
3.2 Programación en Java 78

programador (de clase o tipo referencia). Un tipo primitivo es atómico –esto es,
no contienen a otros campos o atributos– y es aquel cuyas variables no se refieren
a objetos. Se encuentran con un valor válido directamente en la memoria donde se
ejecuta el programa. En la tabla 3.2 se encuentra una lista con los tipos primitivos
y los rangos de valores que pueden almacenar.
Tabla 3.2 Tipos primitivos y sus rangos
Nombre del tipo Representación Capacidad
boolean 16 bits true o false
char 16 bits Unicode 2.0
byte 8 bits con signo -128 ... 127
en complemento a 2
short 16 bits con signo -32768...32767
en complemento a 2
int 32 bits con signo 216...216  1
en complemento a 2
long 64 bits con signo 263...263  1
en complemento a 2
float 32 bits de acuerdo al 2149 ...p2  223 q  2127
estándar IEEE 754-1985
double 64 bits de acuerdo al 21074 ...p2  252 q  21023
estándar IEEE 754-1985
Otro tipo de dato que vamos a usar mucho, pero que corresponde a una clase y
no a un dato primitivo como en otros lenguajes, es el de las cadenas que consisten
de una sucesión de caracteres. La clase se llama String. Las cadenas (String) son
cualquier sucesión de caracteres, menos el de fin de lı́nea, entre comillas. Los
siguientes son objetos tipo String:
"Esta es una cadena 1 2 3 "
""
La primera es una cadena común y corriente y la segunda es una cadena vacı́a,
que no tiene ningún carácter.
La operación fundamental con cadenas es la concatenación, que se representa
con el operador +. Podemos construir una cadena concatenando (“sumando”) dos
o más cadenas:
"a"+"b"+"c" "abc"
"Esta cadena es"+"muy bonita " "Esta cadena esmuy bonita "
Una de las ventajas del operador de concatenación de cadenas es que fuerza a
enteros a convertirse en cadenas cuando aparecen en una expresión de concatena-
ción de cadenas. Por ejemplo, si tenemos una variable LIM que vale 12, tenemos
lo siguiente:
79 Clases y objetos

"El lı́mite es: "+ LIM + "." se guarda como "El lı́mite es: 12."

Hablaremos mucho más de la clase String en lo que sigue.


Nos falta por describir la parte correspondiente a xParámetrosy. En la figu-
ra 3.12 damos la sintaxis y semántica para la declaración de esta parte del enca-
bezado de un método.
Figura 3.12 Especificación de parámetros
Sintaxis:
xParámetrosy::= ∅ |
xparámetroy(, xparámetroy)*
xparámetroy ::= xtipoy xidentificadory
Semántica:
Los parámetros pueden estar ausentes o bien consistir de una lista de
parámetros separados entre sı́ por comas. Un parámetro marca lugar y
tipo para la información que se le dé al método. Lo que le interesa al
compilador es la lista de tipos (sin identificadores) para identificar a un
método dado, ya que se permite más de un método con el mismo nombre,
pero con distinta firma.

Cuando invocamos a un método le tenemos que proporcionar (pasar ) el número


y tipo de argumentos que define su firma, en el orden definido por la firma. A esto
se le conoce como paso de parámetros. En general se definen tres objetivos en el
paso de parámetros: parámetros de entrada, parámetros de salida y parámetros
tanto para entrada como para salida. A estos tres tipos se asocian mecanismos de
paso de parámetros, entre los que están:
Paso por valor: Se evalúa al argumento y se pasa nada más unas copia de ese
valor; este tipo de paso de parámetro está asociado a los parámetros para
entrada. La evaluación se hace en el momento de la llamada al método, por
lo que se hace “lo antes posible”.
Paso por referencia: Se toma la dirección en memoria donde se encuentra la
variable y eso es lo que se pasa como argumento. Se piensa en pasar paráme-
tros por referencia cuando se les va a usar para salida.
Evaluación perezosa o por necesidad: No se evalúa el argumento hasta que
se vaya usar dentro de la implementación del método; evita también evalua-
ciones repetidas del mismo argumento; se dice que se evalúa el parámetro
“lo más tarde posible”.
Paso por nombre: Parecida a la evaluación perezosa, se le pasa al método el
“nombre” del parámetro y lo evalúa en cada uso dentro del método. Si en
el flujo de la ejecución no aparece la expresión con el argumento, éste no
3.2 Programación en Java 80

se evalúa. Se evalúa cada vez que aparece en el punto donde aparece, a


diferencia de la evaluación perezosa que lo hace una única vez.
En el caso de Java todos los argumentos se pasan por valor, incluyendo a las
referencias a los objetos. Eso quiere decir que no podemos modificar la referencia
a un objeto, pero como tenemos la dirección del objeto podemos acudir a él y
modificarlo. Aclararemos más este punto cuando lo enfrentemos.
Por ejemplo, los métodos de una Manecilla que dan los valores de los atributos
privados tienen firmas como se muestra en el listado 3.4 en la página opuesta.
En general podemos pedirle a cualquier método que regrese un valor y tendrı́a
entonces la sintaxis de los métodos de acceso. Los métodos de acceso para la clase
Reloj, como los atributos son objetos de la clase Manecilla, regresan un valor que
corresponde a la clase antes mencionada y que se muestra en el listado 3.3. No es
claro, sin embargo, que queramos acceso a las manecillas como tales, por lo que
no van a usarse estos métodos.

Código 3.3 Métodos de acceso para los atributos privados de Reloj (ServiciosReloj)

100 package R e l o j ;
200 /∗ ∗
300 ∗ I n t e r f a c e <code>S e r v i c i o s R e l o j </code> d e s c r i b e l o s s e r v i c i o s
400 ∗ que da un r e l o j d i g i t a l .
500 ∗
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
700 ∗ @ v e r s i o n 1 . 0
800 ∗/
900 p u b l i c i n t e r f a c e S e r v i c i o s R e l o j {
1000
1100 /∗ ∗
1200 ∗ Método <code>g e t H o r a s </code >. D e v u e l v e l a r e f e r e n c i a a l o b j e t o
1300 ∗ que r e p r e s e n t a a l a m a n e c i l l a de l a s h o r a s .
1400 ∗ @ r e t u r n v a l o r t i p o <code>M a n e c i l l a </code >: l a s h o r a s .
1500 ∗/
1600 public Manecilla getHoras ( ) ;
1700
1800 /∗ ∗
1900 ∗ Método <code>g e t M i n u t o s </code >. D e v u e l v e l a r e f e r e n c i a a l
2000 ∗ o b j e t o que r e p r e s e n t a a l a m a n e c i l l a de l o s m i n u t o s .
2100 ∗ @ r e t u r n v a l o r t i p o <code>M a n e c i l l a </code >: l o s m i n u t o s .
2200 ∗/
2300 public Manecilla getMinutos ( ) ;
...
3000 } // S e r v i c i o s R e l o j
81 Clases y objetos

Código 3.4 Métodos de acceso para los atributos privados de Manecilla ServiciosManecilla

100 package R e l o j ;
200 /∗ ∗
300 ∗ I n t e r f a c e <code>S e r v i c i o s M a n e c i l l a </code> d e s c r i b e l o s s e r v i c i o s
400 ∗ que da una m a n e c i l l a de un r e l o j d i g i t a l .
500 ∗
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
700 ∗ @ v e r s i o n 1 . 0
800 ∗/
900 p u b l i c i n t e r f a c e S e r v i c i o s M a n e c i l l a {
1000
1100 /∗ ∗
1200 ∗ Método <code>g e t V a l o r </code >. Accede a l a t r i b u t o
1300 ∗ <code>v a l o r </code> y m u e s t r a e l v a l o r que t i e n e .
1400 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >: <code>v a l o r </code >.
1500 ∗/
1600 public int getValor ( ) ;
1700
1800 /∗ ∗
1900 ∗ Método <code>getLIMITE </code >. Accede a l a t r i b u t o
2000 ∗ <code>LIMITE</code> y m u e s t r a e l v a l o r que t i e n e .
2100 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >: <code>LIMITE</code >.
2200 ∗/
2300 p u b l i c i n t getLIMITE ( ) ;
...
4800 } // S e r v i c i o s M a n e c i l l a

Métodos de implementación
Estos métodos son los que dan los servicios, pero que hacen algo más que
simplemente informar sobre el estado del objeto. Por ello, el método muestra cuya
firma aparece en el listado 3.5 en la siguiente página es de este tipo. Es común
que este tipo de métodos regresen un valor que indique algún resultado de lo que
hicieron, o bien que simplemente avisen si pudieron o no hacer lo que se les pidió,
regresando un valor booleano. En el caso de que sea seguro que el método va
a poder hacer lo que se le pide, sin contratiempos ni cortapisas, y no se desee
que regrese algún valor4 que haya calculado, se indica que no regresa ningún
valor, poniendo en lugar de xtipoy la palabra void. Por ejemplo, el encabezado
del método que muestra la Manecilla debe mostrar la posición de la manecilla, lo
que denotamos con una cadena y queda como se muestra en el listado 3.5 en la
siguiente página.
4
Un valor en este contexto se refiere también a la referencia a un objeto de alguna clase
especificada, como es el caso de una cadena de caracteres.
3.2 Programación en Java 82

Código 3.5 Métodos de implementación para Manecilla ServiciosManecilla

100 package R e l o j ;
/∗ ∗ . . . Documentación de l a i n t e r f a z . . .
900 p u b l i c i n t e r f a c e S e r v i c i o s M a n e c i l l a {
/∗ . . . Métodos r e g i s t r a d o s h a s t a a h o r a . . . ∗/
...
2500 /∗ ∗
2600 ∗ Método <code>muestra </code >. Mu e st r a en una c a d e n a l a p o s i c i ó n
2700 ∗ de l a m a n e c i l l a .
2800 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code >: e l v a l o r de l a
2900 ∗ manecilla .
3000 ∗/
3100 public S t r i n g muestra ( ) ;
...
4800 } // S e r v i c i o s M a n e c i l l a

Otra vez aparecen entre <code> y </code> lo que va a aparecer en la página


web de la clase o interfaz con tipo de letra distinto.
También en el listado 3.6 mostramos el encabezado del método de implemen-
tación muestra para la interfaz ServiciosReloj, que en este caso simplemente va a
mostrar en el dispositivo de salida el estado del reloj, por lo que no entrega ningún
valor.

Código 3.6 Métodos de implementación de Reloj ServiciosReloj

100 package R e l o j ;
/∗ ∗ . . . Documentación de l a i n t e r f a z . . .
900 p u b l i c i n t e r f a c e S e r v i c i o s R e l o j {
/∗ . . . Métodos ya d e c l a r a d o s . . . ∗/
...
2500 /∗ ∗
2600 ∗ Método <code>muestra </code >. ( de i m p l e m e n t a c i ó n ) . Mu e st r a en
2700 ∗ e l d i s p o s i t i v o de s a l i d a l a h o r a que t e n g a marcada e l r e l o j .
2800 ∗/
2900 public void muestra ( ) ;
...
4300 } // S e r v i c i o s R e l o j

Mientras que el método muestra de la manecilla muestra su valor en una ca-


dena, el método con el mismo nombre del reloj va a hacer su trabajo sin regresar
ningún valor. Ninguno de los dos métodos tiene parámetros, ya que toda la infor-
mación que requerirá es el estado del objeto, al que tienen acceso por ser métodos
de la clase.
83 Clases y objetos

Métodos mutantes o de modificación


Los métodos mutantes o de modificación son, como ya mencionamos, aque-
llos que cambian el estado de un objeto. Generalmente tienen parámetros, pues
requieren información de cómo modificar el estado del objeto. Los métodos que
incrementan y que asignan un valor son de este tipo, aunque el método que incre-
menta no requiere de parámetro ya que el valor que va a usar como incremento
es la unidad (1). Muchas veces queremos que el método también nos proporcio-
ne alguna información respecto al cambio de estado, como pudiera ser un valor
anterior o el mismo resultado; también podrı́amos querer saber si el cambio de
estado procedió sin problemas. En estos casos el método tendrá valor de regreso,
mientras que si no nos proporciona información será un método de tipo void. Por
ejemplo, el método que incrementa, de la interfaz para Manecilla nos interesa saber
si al incrementar llegó a su lı́mite. Por ello conviene que regrese un valor de 0 si
no llegó al lı́mite y de 1 si es que llegó (dio toda una vuelta). En el caso de Reloj,
como el incremento no incide más allá de él, no hay necesidad de que regrese un
valor. Las firmas de estos métodos se muestran en los listados 3.7 a continuación
y 3.8 en la siguiente página.

Código 3.7 Métodos mutantes para Reloj ServiciosReloj

100 package R e l o j ;
/∗ ∗ . . . d o c u m e n t a c i ó n de l a i n t e r f a z . . .
...
900 public interface S e r v i c i o s R e l o j {
/∗ ∗ . . . e n c a b e z a d o s h a s t a muestra . . . ∗/
...
3100 /∗ ∗
3200 ∗ Método <code>i n c r e m e n t a </code >, i n c r e m e n t a e l r e l o j en s u
3300 ∗ u n i d a d de t i e m p o más peque ña .
3400 ∗/
3500 public void incrementa ( ) ;
3600
3700 /∗ ∗
3800 ∗ Método <code>s e t V a l o r </code> e s t a b l e c e nueva h o r a p a r a e l r e l o j .
3900 ∗ @param n v o H o r a s de t i p o <code>i n t </code> nuevo v a l o r de h o r a s .
4000 ∗ @param nvoMins de t i p o <code>i n t </code> nuevo v a l o r de m i n u t o s .
4100 ∗/
4200 p u b l i c v o i d s e t V a l o r ( i n t nvoHoras , i n t nvoMins ) ;
4300 } // S e r v i c i o s R e l o j
3.3 Implementación de los servicios (clases) 84

Código 3.8 Métodos mutantes para Manecilla ServiciosManecilla

100 package R e l o j ;
/∗ ∗ . . . d o c u m e n t a c i ó n de l a i n t e r f a z . . .
900 public interface S er v i ci os Ma n ec i l l a {
/∗ . . . e n c a b e z a d o s h a s t a muestra . . . ∗/
...
3300 /∗ ∗
3400 ∗ Método <code>i n c r e m e n t a </code> i n c r e m e n t a en una u n i d a d e l
3500 ∗ v a l o r de l a m a n e c i l l a .
3600 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >:
3700 ∗ 0 s i no c o m p l e t ó una v u e l t a con e l i n c r e m e n t o .
3800 ∗ 1 s i c o m p l e t ó una v u e l t a c o m p l e t a .
3900 ∗/
4000 public int incrementa ( ) ;
4100
4200 /∗ ∗
4300 ∗ Método <code>s e t V a l o r </code> m o d i f i c a e l v a l o r de <code>v a l o r </code >.
4400 ∗ @param n v o V a l o r t i p o <code>i n t </code >, e l v a l o r que s e d e s e a
4500 ∗ registrar .
4600 ∗/
4700 public void s e t V a l o r ( i n t nvoValor ) ;
4800 } // S e r v i c i o s M a n e c i l l a

Noten que el método setValor de ServiciosManecilla tiene un parámetro, que es


el nuevo valor que va a tomar la manecilla, mientras que el método con el mismo
nombre de la clase ServiciosReloj tiene dos parámetros, ya que requiere los valores
para las horas y para los minutos.

3.3 Implementación de los servicios (clases)

Ya que tenemos los servicios que se deben proveer para que tengamos un reloj
y las manecillas del mismo, pasamos a construir las clases que van a implementar
esos servicios. Lo primero que veremos es aquella información que determina el
estado de cada uno de los objetos que vayamos a querer construir. Entraremos ya
al contexto de definir las clases Reloj y Manecilla para determinar el cómo y el con
qué. El cómo nos lo da la implementación de los métodos, junto con los métodos
auxiliares y constructores. El “con qué” nos lo dan los atributos de las clases.
Identificamos dos clases en nuestro sistema, Manecilla y Reloj. Como la clase
Manecilla no se usará más que dentro de Reloj, la ponemos en el mismo archivo
que a Reloj, pero dejando a la clase Reloj como la primera en el archivo, ya que
será invocada desde fuera del archivo. El archivo se llamará Reloj.java porque la
85 Clases y objetos

clase Reloj es la que puede existir como un todo, como ente auto contenido de la
aplicación.

Atributos
Antes de definir la implementación de los métodos que listamos en las interfaces
trabajemos con los atributos que dimos en las tarjetas, que son los que definen el
estado de un objeto. Los sustantivos deberán ser atributos (variables o constantes)
–datos– mientras que los verbos fueron métodos –procesos o cálculos–. Lo primero
que tenemos que hacer es determinar el espacio en memoria para los objetos
o datos primitivos que se encuentran en cada clase. Esto lo hacemos mediante
una declaración. En la declaración especificamos el nombre que le queremos dar
al atributo –ya sea objeto o primitivo– y el tipo que va a tener –entero, tipo
Manecilla, etc.–. También debemos especificar el acceso a cada atributo. Veamos la
sintaxis y semántica de una declaración de atributo (dato, campo) en la figura 3.13.
Figura 3.13 Declaración de un atributo
Sintaxis:
xdeclaración de atributoy ::= xaccesoy xmodificadory xtipo y
xidentificador y(,xidentificadory)*;
xmodificador y ::= final | static | ∅
xtipo y ::= xtipo primitivoy | xidentificador de clase y
Semántica:
Todo identificador que se declara, como con el nombre de las clases, se
le debe dar el xaccesoy y si es constante (final) o no. Por el momento no
hablaremos de static. También se debe decir su tipo, que es de alguno de
los tipos primitivos que tiene Java, o bien, de alguna clase a la que se
tenga acceso; lo último que se da es el identificador. Se puede asociar una
lista de identificadores separados entre sı́ por una coma, con una misma
combinación de acceso, modificador y tipo, y todas las variables de la lista
tendrán las mismas caracterı́sticas. Al declararse un atributo, el sistema de
la máquina le asigna una localidad, esto es, un espacio en memoria donde
guardar valores del tipo especificado. La cantidad de espacio depende del
tipo. A los atributos que se refieren a una clase se les reserva espacio para
una referencia, que es la posición en el heap 5 donde quedará el objeto que
se asocie a esa variable.

5
El heap es un espacio de memoria que la Máquina Virtual de Java reserva para colocar
ahı́ a todos los objetos que construye. Tiene un administrador de memoria que se encarga de
reutilizar el espacio cuando sabe que un objeto ya no va a ser utilizado.
3.3 Implementación de los servicios (clases) 86

Declaremos los atributos que se presentan en la aplicación que estamos arman-


do en los listados 3.9 y 3.10.
Código 3.9 Declaración de atributos de la clase Reloj Reloj

100 package R e l o j ;
200 /∗ ∗
300 ∗ C l a s e <code>R e l o j </code >. I m p l e m e n t a un r e l o j d i g i t a l .
400 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
500 ∗ @ v e r s i o n 1 . 0
600 ∗/
700 p u b l i c c l a s s R e l o j implements S e r v i c i o s R e l o j {
800 private Manecilla horas ; // Para p o s i c i ó n de h o r a r i o
900 private Ma n e c i l l a minutos ; // Para p o s i c i ó n de m i n u t e r o
...
9100 } // c l a s e R e l o j

Código 3.10 Declaración de atributos de la clase Manecilla Manecilla

100 package R e l o j ;
200 /∗ ∗
300 ∗ C l a s e <code>M a n e c i l l a </code >. I m p l e m e n t a a cada una de l a s
400 ∗ m a n e c i l l a s d e l r e l o j .
500 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
600 ∗ @ v e r s i o n 1 . 0
700 ∗/
800 c l a s s M a n e c i l l a implements S e r v i c i o s M a n e c i l l a {
900 p r i v a t e i n t v a l o r ; // Guarda l a p o s i c i ó n
1000 p r i v a t e f i n a l i n t LIMITE ; // Guarda v a l o r máximo (  1)
...
7900 } // C l a s e M a n e c i l l a

Las declaraciones de las lı́neas 800 y 900 en el listado 3.9, y 900 y 1000 en el
listado 3.10 son declaraciones de atributos del tipo que precede al identificador.
En las lı́neas 800 y 900 del listado 3.9 se están declarando dos atributos de tipo
Manecilla y acceso privado, mientras que el tipo de la variable declarada en la lı́nea
900 del listado 3.10 es entero (int) también privado. En la lı́nea 1000 del listado 3.10
aparece el modificador final, que indica que a este atributo, una vez asignado un
valor por primera vez, este valor ya no podrá ser modificado, es una constante
–como veremos, este valor debe ser asignado en el constructor–. Siguiendo las
reglas de etiqueta de Java, el identificador tiene únicamente mayúsculas. En el
caso de los atributos de tipo Manecilla, debemos tener claro que nada más estamos
declarando un atributo, una variable, no estamos construyendo el objeto. Esto
quiere decir que cuando se construya el objeto de tipo Manecilla, la variable horas
se referirá a este objeto, contendrá una referencia a un objeto de tipo Manecilla.
87 Clases y objetos

Como los objetos pueden tener muy distintos tamaños serı́a difı́cil acomodarlos
en el espacio de ejecución del programa, por lo que se construyen siempre en un
espacio de memoria destinado a objetos, que se llama heap y del que hablaremos
a detalle más adelante; la variable asociada a ese objeto nos dirá la dirección del
(referencia al) objeto en el heap. Cuando una variable representa a un objeto (por
construir), el tipo de la variable es el de una referencia cuyo valor es una dirección
del heap. En esa dirección se encuentra el objeto construido.
Como mencionamos antes, en las interfaces no se registran métodos auxiliares
o constructores. Pero en las clases que implementen a estas interfaces (puede haber
más de una por cada interfaz) debemos incluir a los métodos constructores y a
los métodos auxiliares. La declaración de la implementación de un método en una
clase es casi igual que en las interfaces, excepto que al encabezado le sigue la
implementación del método entre llaves. Veamos cómo queda lo que llevamos del
programa en los listados 3.11 abajo y 3.12 en la siguiente página6 .

Código 3.11 Esqueleto para la implementación de Reloj Reloj (1/2)

3500 /∗ ∗
3600 ∗ Método <code>muestra </code >. ( de i m p l e m e n t a c i ó n ) . Mu e st r a en
3700 ∗ e l d i s p o s i t i v o de s a l i d a l a h o r a que t e n g a marcada e l r e l o j .
3800 ∗/
3900 public void muestra ( ) {
/∗ i m p l e m e n t a c i ó n ∗/
5400 } // f i r m a : m u e s t r a ( )
5500
5600 /∗ ∗
5700 ∗ Método <code>i n c r e m e n t a </code >, i n c r e m e n t a e l r e l o j en s u
5800 ∗ u n i d a d de t i e m p o más peque ña .
5900 ∗/
6000 public void incrementa ( ) {
/∗ i m p l e m e n t a c i ó n ∗/
6200 } // f i r m a : i n c r e m e n t a ( )
6300
6400 /∗ ∗
6500 ∗ Método <code>s e t V a l o r </code> e s t a b l e c e nueva h o r a p a r a e l r e l o j .
6600 ∗ @param n v o H o r a s de t i p o <code>i n t </code >, e l nuevo v a l o r p a r a
6700 ∗ l a s horas .
6800 ∗ @param nvoMins de t i p o <code>i n t </code> e l nuevo v a l o r p a r a l o s
6900 ∗ minutos .
7000 ∗/
7100 p u b l i c v o i d s e t V a l o r ( i n t nvoHoras , i n t nvoMins ) {
/∗ i m p l e m e n t a c i ó n ∗/
7400 } // f i r m a : s e t V a l o r ( i n t , i n t )

6
En adelante únicamente listamos, respetando la numeración, las lı́neas involucradas; el orden
exacto está dado en términos de lograr una mejor distribución en el libro.
3.3 Implementación de los servicios (clases) 88

Código 3.11 Esqueleto para la implementación de Reloj Reloj (2/2)

7600 /∗ ∗
7700 ∗ Método <code>g e t H o r a s </code : R e g r e s a a l a m a n e c i l l a .
7800 ∗ @ r e t u r n v a l o r de t i p o <code>M a n e c i l l a </code >.
7900 ∗/
8000 public Manecilla getHoras () {
/∗ i m p l e m e n t a c i ó n ∗/
8200 }
8300
8400 /∗ ∗
8500 ∗ Método <code>g e t M i n u t o s </code >: R e g r e s a a l a M a n e c i l l a .
8600 ∗ @ r e t u r n v a l o r de t i p o <code>M a n e c i l l a </code >.
8700 ∗/
8800 public Manecilla getMinutos () {
/∗ i m p l e m e n t a c i ó n ∗/
9000 }

Código 3.12 Esqueleto para la implementación de Manecilla Manecilla (1/2)

3300 /∗ ∗
3400 ∗ Método <code>i n c r e m e n t a </code> i n c r e m e n t a en una u n i d a d e l
3500 ∗ v a l o r de l a m a n e c i l l a .
3600 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >:
3700 ∗ 0 s i no c o m p l e t ó una v u e l t a con e l i n c r e m e n t o .
3800 ∗ 1 s i c o m p l e t ó una v u e l t a c o m p l e t a .
3900 ∗/
4000 public int incrementa () {
/∗ i m p l e m e n t a c i ó n ∗/
4300 } // f i r m a : i n c r e m e n t a ( )
4400
4500 /∗ ∗ Método <code>s e t V a l o r </code >, a c t u a l i z a v a l o r de l a m a n e c i l l a .
4600 ∗ @param n v o v a l o r t i p o <code>i n t </code >: nuevo v a l o r p a r a
4700 ∗ la manecilla .
4800 ∗/
4900 public void s e t V a l o r ( i n t v a l o r ) {
/∗ i m p l e m e n t a c i ó n ∗/
5200 } // f i r m a s e t V a l o r ( i n t )
5300
5400 /∗ ∗
5500 ∗ Método <code>g e t V a l o r </code >: r e g r e s a e l v a l o r d e l a t r i b u t o .
5600 ∗ @ r e t u r n un <code>i n t </code >.
5700 ∗/
5800 public int getValor () {
/∗ i m p l e m e n t a c i ó n ∗/
6000 } // f i r m a : g e t V a l o r ( )
89 Clases y objetos

Código 3.12 Esqueleto para la implementación de Manecilla Manecilla (2/2)

6200 /∗ ∗
6300 ∗ Método <code>getLIMITE </code >. Accede a l a t r i b u t o
6400 ∗ <code>LIMITE</code> y m u e s t r a e l v a l o r que t i e n e .
6500 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >. E l v a l o r d e l a t r i b u t o .
6600 ∗/
6700 p u b l i c i n t getLIMITE ( ) {
/∗ i m p l e m e n t a c i ó n ∗/
6900 } // f i r m a : getLIMITE ( )
7000
7100 /∗ ∗ Método <code>muestra </code >. Mu e st r a en una c a d e n a l a p o s i c i ó n
7200 ∗ de l a m a n e c i l l a .
7300 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: e l v a l o r de l a
7400 ∗ manecilla .
7500 ∗/
7600 public S t r i n g muestra ( ) {
/∗ i m p l e m e n t a c i ó n ∗/
7900 } // f i r m a : m u e s t r a ( )

Como declaramos que nuestras clases Reloj y Manecilla implementan, respec-


tivamente, a las interfaces ServiciosReloj y ServiciosManecilla, estas clases tendrán
que proporcionar las implementaciones para los métodos que listamos en las in-
terfaces correspondientes. El esqueleto construido hasta ahora se puede ver en los
listado 3.12.
En las interfaces no se presentan otra categorı́a de métodos, ya que las inter-
faces no describen objetos, sino únicamente los contratos a los que se obligan las
clases que los implementen y que corresponden a métodos públicos a los que se
puede acceder desde fuera de las clases.
De las cinco variedades de métodos que listamos, nos falta revisar a los métodos
constructores y a los métodos auxiliares, que tienen sentido sólo en el contexto de
la definición de clases.

Métodos auxiliares
Estos métodos son aquellos que auxilian a los objetos para llenar las solicitudes
que se les hacen. Pueden o no regresar un valor, y pueden o no tener parámetros:
depende de para qué se vayan a usar. Dado que el problema que estamos atacando
por el momento es relativamente simple, no se requieren métodos auxiliares para
las clases.
3.3 Implementación de los servicios (clases) 90

Métodos constructores
Una clase es un patrón (descripción, modelo, plano) para la construcción de
objetos que sean ejemplares (instances) de esa clase. Por ello, las clases sı́ tie-
nen constructores que determinan el estado inicial de los objetos construidos de
acuerdo a esa clase.
En Java los métodos constructores tienen una sintaxis un poco distinta a la
de otros tipos de métodos. Ésta se puede ver en la figura 3.14.

Figura 3.14 Encabezado de un constructor


Sintaxis:
xconstructory ::=xaccesoy xidentificador de Clasey ( xParámetrosy ) {
ximplementacióny
}
xParámetrosy ::=xparámetroy(, xparámetroy) | ∅
xparámetroy ::= xtipoy xidentificadory
Semántica:
Los constructores de una clase son métodos que consisten en un acceso
–que puede ser cualquiera de los dados anteriormente– seguido del nombre
de la clase y entre paréntesis los xParámetrosy del método. Un parámetro
corresponde a un dato que el método tiene que conocer (o va a modificar).
Cada parámetro deberá tener especificado su tipo. Los nombres dados a ca-
da parámetro pueden ser arbitrarios, aunque se recomienda, como siempre,
que sean nemónicos y no se pueden repetir.

Un constructor es el que permite la creación o construcción (instancing) de un


objeto (un ejemplar) de una clase dada, para que sea asociado a (referido por) una
variable de ese tipo. Su objetivo principal es el de establecer el estado inicial del
objeto (inicializar los atributos, que son los que definen el estado de un objeto).
Puede recibir para la construcción datos en la forma de parámetros.
Podemos tener tantos constructores como queramos, siempre y cuando se dis-
tingan por sus firmas. Por ejemplo, podemos tener un constructor que no tenga
parámetros, o uno que tenga otra organización con sus parámetros. Un segundo
constructor para Manecilla pudiera ser uno que establece un valor prefijado para
la manecilla. Algo similar podemos hacer con la clase Reloj y lo podemos ver en
los listados 3.13 y 3.14 en la página opuesta. Los constructores siempre tienen
el mismo nombre que la clase de la que son constructores. No es necesario decir
qué tipo de valor regresan, porque “regresan” (construyen) a un objeto de su clase.
91 Clases y objetos

Código 3.13 Firmas de los constructores para la clases Reloj Reloj

1100 /∗ ∗
1200 ∗ Crea un e j e m p l a r nuevo de <code>R e l o j </code >. Pone r a n g o a l a s
1300 ∗ m a n e c i l l a s e i n i c i a en h o r a c e r o .
1400 ∗ @param limH de t i p o <code>i n t </code >, e l lı́ m i t e p a r a h o r a s .
1500 ∗ @param limM de t i p o <code>i n t </code >, e l lı́ m i t e p a r a m i n u t o s .
1600 ∗/
1700 p u b l i c R e l o j ( i n t limH , i n t limM ) {
/∗ i m p l e m e n t a c i ó n ∗/
2000 } // f i r m a : R e l o j ( i n t , i n t )
2100
2200 /∗ ∗
2300 ∗ Crea un e j e m p l a r nuevo de <code>R e l o j </code >. Pone r a n g o a l a s
2400 ∗ m a n e c i l l a s e i n i c i a con h o r a p r e e s t a b l e c i d a .
2500 ∗ @param limH de t i p o <code>i n t </code> .
2600 ∗ @param limM de t i p o <code>i n t </code> .
2700 ∗ @param h r s de t i p o <code>i n t </code> .
2800 ∗ @param mins de t i p o <code>i n t </code> .
2900 ∗/
3000 p u b l i c R e l o j ( i n t limH , i n t limM , i n t h r s , i n t mins ) {
/∗ i m p l e m e n t a c i ó n ∗/
3300 } // f i r m a : R e l o j ( i n t , i n t , i n t , i n t )

Código 3.14 Firmas de los constructores para la clase Manecilla Manecilla

1300 /∗ ∗
1400 ∗ Crea un e j e m p l a r nuevo de <code>M a n e c i l l a </code >. E s t a b l e c e e l
1500 ∗ rango .
1600 ∗ @param l i m de t i p o <code>i n t </code >, e l r a n g o [ 0 . . l i m  1 ] .
1700 ∗/
1800 Manecilla ( int lim ) {
/∗ i m p l e m e n t a c i ó n ∗/
2000 } // M a n e c i l l a ( i n t , i n t )
2100
2200 /∗ ∗
2300 ∗ Crea un e j e m p l a r nuevo de <code>M a n e c i l l a </code >. E s t a b l e c e e l
2400 ∗ r a n g o y l a p o s i c i ó n i n i c i a l .
2500 ∗ @param l i m de t i p o <code>i n t </code >, r a n g o [ 0 . . l i m  1]
2600 ∗ @param v a l de t i p o <code>i n t </code >, v a l o r a e s t a b l e c e r .
2700 ∗/
2800 M a n e c i l l a ( i n t lim , i n t v a l ) {
/∗ i m p l e m e n t a c i ó n ∗/
3100 } // M a n e c i l l a ( i n t , i n t )
3.3 Implementación de los servicios (clases) 92

Es importante notar que la firma de un método consiste únicamente del nombre


del método junto con los tipos de los parámetros; el encabezado de un método es
el que contiene al tipo de valor que regresa el método precediendo a la firma. Por
lo tanto, los dos encabezados que se encuentran en el listado 3.15 tienen la misma
firma y el compilador darı́a un mensaje de método duplicado, aunque el nombre
de los parámetros sea distinto.

Código 3.15 Métodos con la misma firma


public R e l o j ( i nt hrs , int mins , i n t limH , i n t limM )
// Firma : R e l o j ( i n t , int , int , int )
public R e l o j ( i n t lim1 , int lim2 , int val1 , int val2 )
// f i r m a : R e l o j ( i n t , int , int , int )

Toda clase tiene un constructor por omisión, sin parámetros, que puede ser in-
vocado, siempre y cuando no se haya declarado ningún constructor para la clase.
Esto es, si se declaró, por ejemplo, un constructor con un parámetro, el construc-
tor sin parámetros ya no está accesible. Por supuesto que el programador puede
declarar un constructor sin parámetros que sustituya al que proporciona Java por
omisión y que podrı́a codificarse de la siguiente manera:

p u b l i c M a n e c i l l a ( ) { /∗ c u e r p o v a cı́ o ∗/ }

El estado inicial que da el constructor por omisión al objeto es el valor cero (0)
en los atributos numéricos, falso en los atributos lógicos y referencia nula (null)
en los atributos que son objetos (referencias a objetos).

El método main
El método main corresponde a la colaboración que queremos se dé entre clases.
En él se define la lógica de ejecución. No toda clase tiene un método main, ya que
no toda clase va a definir una ejecución de la aplicación, aunque también puede
usarse para probar que determinadas clases trabajan bien, invocándolas desde
el método main. El sistema operativo (la máquina virtual de Java) reconoce al
método main y si se “invoca” a una clase cualquiera desde el sistema operativo,
éste procede a ejecutar ese método; si la clase no tiene un método main la ejecución
abortará con un mensaje de error. El encabezado para este método se encuentra
en el listado 3.16 en la página opuesta.
93 Clases y objetos

Código 3.16 Encabezado para el método main


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
/∗ ximplementación y ∗/
} // main

Se ve bastante complicado, aunque no lo es tanto. El significado de void y


public ya lo sabemos. Lleva el modificador static porque el método main es un
método que existe sólo para la clase, no para cada objeto de la misma. Cuando un
atributo o un método tienen este modificador, quiere decir que todos los objetos
que se construyan de esa misma clase van a compartir a ese atributo o método.
Finalmente, el parámetro que tiene es un arreglo (denotado por [ ]) de cadenas
(String) que son las cadenas que aparecen en la lı́nea de comandos cuando se invoca
desde el sistema operativo (un arreglo es simplemente una sucesión, un vector de
datos, que se distinguen entre sı́ por la posición que ocupan en la sucesión; se
puede obtener el valor de cada elemento dando, entre corchetes, el lugar que
ocupan, como en args[0] que nos darı́a el primer elemento). La implementación
de este método es como la de cualquier otro. Cuando veamos implementación en
general daremos las restricciones que presenta por tratarse de un método estático
(static).

3.3.1. Alcance de los identificadores


Para estos momentos ya tenemos bastantes nombres en el programa; algunos
se repiten, como el nombre de la clase en el nombre de los constructores o los
identificadores de los parámetros en métodos distintos. Es importante saber cada
nombre, asignado a una variable particular, qué alcance tiene, esto es, desde dónde
puede el programa referirse a cada uno de los nombres y cómo distingue entre
distintas variables con el mismo nombre.
La pista más importante para determinar el alcance de un identificador está da-
da por las parejas de llaves que abren y cierran las definiciones de la clase y de
cada uno de los métodos (más adelante veremos también alcances definidos en al-
gunos enunciados). Para las que corresponden a la clase, todos los nombres que se
encuentran en las declaraciones no estáticas dentro de la clase son accesibles desde
cualquier método no estático de la misma clase. Adicionalmente, los nombres que
tengan acceso público o de paquete son accesibles también desde fuera de la clase.
En el caso de los parámetros, las variables pueden ser referidas únicamente dentro
del método del que son parámetros. Si algún identificador usado como nombre
de atributo se usa como nombre de parámetro, dentro del método en cuestión el
identificador se referirá al parámetro, no al atributo.
3.3 Implementación de los servicios (clases) 94

Sin embargo, hemos dicho que una clase es nada más una plantilla para cons-
truir objetos, y que cada objeto que se construya va a ser construido de acuerdo
a esa plantilla. Esto quiere decir que, por ejemplo, en el caso de la clase Manecilla,
cada objeto que se construya va a tener su atributo valor y su atributo LIMITE. Si
éste es el caso, ¿cómo hacemos desde fuera de la clase para saber de cuál objeto
estamos hablando? Muy fácil: anteponiendo el nombre del objeto al del atributo,
separados por un punto. Veamos la forma precisa en la figura 3.15.
Figura 3.15 Acceso a atributos o métodos de objetos
Sintaxis:
xReferencia a atributo o métodoy::=
(xreferencia de objeto o clasey.) (xid de atributoy |
xinvocación a métodoy)
Semántica:
El operador . es un operador binario, infijo, de selección y asocia de izquier-
da a derecha. Lo usamos para identificar: el identificador que se encuentra
a su derecha pertenece al objeto a su izquierda. También podemos usarlo
para identificar a alguna clase que pertenezca a un paquete. En el caso
de un identificador de método, éste deberá presentarse con los argumentos
correspondientes entre paréntesis. la xreferencia de objetoy puede aparecer
en una variable o como resultado de una función que regrese como valor
una referencia, que se encuentre en el alcance de este enunciado. Podemos
pensar en el . como un operador que trabaja sobre referencias.

Si tenemos en la clase Reloj dos objetos que se llaman, respectivamente, horas


y minutos, podremos acceder a sus métodos públicos como incrementa como se
muestra en el listado 3.17.

Código 3.17 Acceso a atributos de los objetos


horas . incrementa ()
minutos . incrementa ()

Es claro que para que se puedan invocar estos métodos desde la clase Reloj
deben tener acceso público o de paquete. También los objetos horas y minutos
tienen que ser conocidos dentro de la clase Reloj.
Sin embargo, cuando estamos escribiendo la implementación de algún método,
al referirnos, por ejemplo, al atributo valor no podemos saber de cuál objeto,
porque el método va a poder ser invocado desde cualquier objeto de esa clase.
Pero estamos suponiendo que se invoca, forzosamente, con algún objeto. Entonces,
95 Clases y objetos

para aclarar que es el atributo valor del objeto con el que se está invocando,
identificamos a este objeto con this. Cuando no aparece un identificador de objeto
para calificar a un atributo, dentro de los métodos de la clase se supone entonces
al objeto this. En el código que sigue las dos columnas son equivalentes para
referirnos a un atributo dentro de un método de la clase, siempre y cuando el
nombre del atributo no aparezca como parámetro de ese método.
this.incrementa() incrementa()
this.valor valor
this.horas.LIM horas.LIM

En cuanto a los parámetros de un método, éstos existen sola y exclusivamente


dentro de la implementación del método, entre las llaves. Son lo que se conoce
como nombres o variables locales: locales al método. Como ya mencionamos, si en
la clase existe algún atributo cuyo nombre sea el mismo que el del parámetro, el
nombre del parámetro oculta al nombre del atributo y para referirse al atributo
dentro del método se tendrá que usar al selector this –véase el listado 3.18–.

Código 3.18 Bloqueo de nombres de atributos


p u b l i c c l a s s R e l o j implements S e r v i c i o s R e l o j {
Ma n e c i l l a horas ,
minutos ;
...
6400 public void s e t V a l o r ( i n t horas , i n t minutos ) {
6500 this . horas . setValor ( horas ) ;
6600 t h i s . minutos . s e t V a l o r ( minutos ) ;
7400 } // R e l o j . s e t V a l o r
...
9100 } // c l a s e R e l o j

En este listado el atributo horas de la clase Reloj se ve “bloqueado” por el


parámetro horas, por lo que para poder ver al atributo hay que rescatar que se
trata del atributo del objeto con el que se esté invocando al método –uso de this
en las lı́neas 6500 y 6600 –.

3.3.2. Implementación de métodos en Java

La implementación de cada uno de los métodos nos va a decir el “cómo” y


“con quién” va a cubrir el objeto ese servicio.
En todo lo que llevamos hasta ahora simplemente hemos descrito los ingredien-
tes de las clases y no hemos todavı́a manejado nada de cómo hacen los métodos
3.3 Implementación de los servicios (clases) 96

lo que tienen que hacer. En general un método va a consistir de su encabezado y


una lista de enunciados entre llaves, como se puede ver en la figura 3.16.
Figura 3.16 Sintaxis para la implementación de un método
Sintaxis:
ximplementacióny ::= xLista de enunciadosy
xLista de enunciadosy ::=xenunciado y xLista de enunciadosy | ∅
xenunciadoy ::= xenunciado simple y; | xenunciado compuesto y
xenunciado simpley ::= xdeclaración localy | xinvocación de método y
| xenunciado de asignacióny
| return | return xexpresióny
Semántica:
La implementación de un método, no importa de cual categorı́a sea, con-
siste de una lista de enunciados entre llaves. Si queremos que el método
no haga nada, entonces no ponemos ningún enunciado entre las llaves.
Los enunciados pueden ser simples o compuestos7 . Un enunciado simple
puede ser una invocación a un método, una declaración de variables, una
asignación de valor a una variable o salir del método regresando (posi-
blemente) un valor. Noten que todos los enunciados simples terminan con
punto y coma –;– sin importar el contexto en el que aparecen. Es impor-
tante mencionar que aquellas variables declaradas en la implementación de
un método, ası́ como los parámetros formales, van a ser accesibles (reco-
nocidas) únicamente dentro de la implementación del método en cuestión,
a diferencia de las variables de la clase –atributos– que van a ser accesibles
(reconocidos) en las implementaciones de cualquiera de los métodos de la
clase.

Las declaraciones
Cuando estamos en la implementación de un método es posible que el método
requiera de objetos o datos primitivos auxiliares dentro del método. Estas varia-
bles auxiliares se tienen que declarar para poder ser usadas. El alcance de estas
variables es únicamente entre las llaves que corresponden al método. Ninguna va-
riable se puede llamar igual que alguno de los parámetros del método, ya que
como los parámetros se consideran como variables locales se estarı́a repitiendo
un identificador en el mismo alcance, lo que causarı́a un error en el momento de
compilar. La sintaxis para una declaración se puede ver en la figura 3.17 en la
página opuesta.
7
No entraremos por ahora a lo que es un enunciado compuesto, ya que todavı́a no los vamos
a usar.
97 Clases y objetos

Figura 3.17 Declaración de variables locales


Sintaxis:
xdeclaración de variable localy ::= xtipoy xLista de identificadoresy;
Semántica:
La declaración de variables locales es muy similar a la de parámetros for-
males, excepto que en este caso sı́ podemos declarar el tipo de varios iden-
tificadores en un solo enunciado. La xLista de identificadoresy es, como su
nombre lo indica, una sucesión de identificadores separados entre sı́ por una
coma (“,”).

Hay que notar que localmente únicamente se pueden declarar variables, ya


sea de tipo primitivo o referencia, y son conocidas, al igual que los parámetros,
únicamente dentro del método en el que se están declarando. No se puede declarar
una variable que repita algún identificador usado para los parámetros, ya que los
parámetros también se comportan, dentro del método, como variables locales. Al
terminar la ejecución del método, estas variables desaparecen.
El único método que requiere de variables auxiliares es el que muestra el reloj,
ya que queremos construir unas cadenas de caracteres para que den el mensaje
de qué hora es. También requerimos de algún objeto que haga de dispositivo de
salida, para poder mostrar ahı́ la hora del reloj; en otras palabras, necesitamos
poder hacer entrada y salida –en realidad, sólo salida–. Este punto lo abordaremos
cuando implementemos el método.

Código 3.19 Declaraciones locales en el método muestra de Reloj Reloj

/∗ ∗ Método <code>muestra </code >. ( de i m p l e m e n t a c i ó n ) . . .


3900 public void muestra ( ) {
4000 // v a r i a b l e s a u x i l i a r e s p a r a m o s t r a r a l u s u a r i o
4100 S t r i n g mensaje1 , mensaje2 , m e n s a j e 3 ;
/∗ i m p l e m e n t a c i ó n ∗/
5400 } // f i r m a : m u e s t r a ( )

La entrada y salida de Java es un poco complicada para ser manejada por


principiantes. Usaremos en una primer instancia un dispositivo de salida, la con-
sola, que Java proporciona automáticamente. Es un objeto de la clase System y se
llama out. En Java a los dispositivos de entrada y salida se les denomina archivos,
independientemente de si van a funcionar en la pantalla, la impresora, el dispo-
sitivo USB o el disco. En el caso del archivo System.out no hay que declararlo,
sino que podemos usarlo directamente. Este objeto es de una clase de archivos
3.3 Implementación de los servicios (clases) 98

que permiten escribir caracteres en la consola y que cuenta con varios métodos
que discutiremos más adelante. Por todo lo anterior, aunque el archivo System.out
no tenga que declararse en ningún punto de nuestra aplicación, las cadenas que
queremos construir sı́ deben aparecer como variables locales del método que va a
mostrar al reloj.

El enunciado return
Cuando un método está marcado para regresar un valor, en cuyo caso el tipo
del método es distinto de void, el método debe tener entre sus enunciados a return
xexpresióny. En el punto donde este enunciado aparezca, el método suspende su
funcionamiento y regresa el valor de la xexpresióny al punto donde apareció su
invocación. Cuando un método tiene tipo void, vamos a utilizar el enunciado re-
turn para salir del método justo en el punto donde aparezca este enunciado. Por
ejemplo, los métodos de acceso lo único que hacen es regresar el valor del atributo,
por lo que quedan como se muestra en el listado 3.20.

Código 3.20 Implementación de los métodos de acceso de la clase Manecilla Manecilla

5800 public int getValor () {


5900 return v a l o r ;
6000 } // f i r m a : g e t V a l o r ( )
...
6700 p u b l i c i n t getLIMITE ( ) {
6800 r e t u r n LIMITE ;
6900 } // f i r m a : getLIMITE ( )

El enunciado de asignación
Tal vez el xenunciado simpley más importante es el xenunciado de asignacióny,
ya que va a ser el que nos va a permitir asignarle un estado inicial a un objeto y la
posibilidad de cambiar ese estado. También es el que nos permite construir objetos
y asociar una variable a cada objeto que construimos. Es conveniente recordar que
las clases son únicamente plantillas para la construcción de objetos. Para que, en
efecto, se realice algo se requiere construir objetos y asociarlos a variables para
que podamos pedirles que hagan algo. El xenunciado de asignacióny se muestra
en la figura 3.18 en la página opuesta.
99 Clases y objetos

Figura 3.18 El enunciado de asignación


Sintaxis:
xenunciado de asignacióny::=xvariabley = xexpresióny
xexpresióny ::= xvariabley | xconstantey
| new xconstructory | ( xexpresióny )
| xoperador unarioy xexpresióny
| xexpresióny xoperador binario y xexpresióny
| xmétodo que regresa valor y
| xenunciado de asignacióny
Semántica:
Podemos decir que el xenunciado de asignacióny consiste de dos partes, lo
que se encuentra a la izquierda de la asignación (=) y lo que se encuentra
a la derecha. A la izquierda tiene que haber una variable, pues es donde
vamos a “guardar”, copiar, colocar un valor. Este valor puede ser, como
en el caso del operador new, una referencia a un objeto en el heap o un
valor. El; valor puede ser de alguno de los tipos primitivos o de alguna de
las clases accesibles. La expresión de la derecha se evalúa (se ejecuta) y el
valor que se obtiene se coloca en la variable de la izquierda. Si la expresión
no es del mismo tipo que la variable, se presenta un error de sintaxis. Toda
expresión tiene que regresar un valor.

La sintaxis de la expresión para la construcción de objetos se encuentra en la


figura 3.19.

Figura 3.19 Construcción de objetos


Sintaxis:
xconstrucción de objetoy ::=new xinvocación método constructory
Semántica:
Para construir un objeto se utiliza el operador new y se escribe a conti-
nuación de él (dejando al menos un espacio) el nombre de alguno de los
constructores que hayamos declarado para la clase, junto con sus argumen-
tos. El objeto queda construido en el heap y tiene todos los elementos que
vienen descritos en la clase.

La invocación de un método constructor o, para el caso de cualquier método


del objeto mismo, se puede ver en la figura 3.20 en la siguiente página.
3.3 Implementación de los servicios (clases) 100

Figura 3.20 Invocación de método


Sintaxis:
xinvocación de métodoy ::= xnombre del métodoy(xArgumentosy )
xArgumentosy ::= xargumentoy (,xargumentoy)* | ∅
xargumentoy ::= xexpresióny
Semántica:
Los xArgumentosy tienen que coincidir en número, tipo y orden con los
xParámetrosy que aparecen en la declaración del método. La sintaxis indica
que si la declaración no tiene parámetros, la invocación no debe tener
argumentos.
Si el método regresa algún valor, entonces la invocación podrá aparecer
en una expresión. Si su tipo es void tendrá que aparecer como enunciado
simple.

El operador new nos regresa una dirección en el heap donde quedó construido
el objeto (donde se encuentran las variables –atributos– del objeto). Tenemos
que guardar esa referencia en alguna variable del tipo del objeto para que se
pueda usar. Si nos lanzamos a programar los constructores de la clase Reloj, lo
hacemos creando (construyendo, instancing) a las manecillas correspondientes.
Presentamos primero los constructores para la clase Manecilla, que está compuesta
únicamente de valores primitivos, que no se tienen que construir ya que se crean
cuando el objeto se construye, por lo que la asignación basta. La implementación
se encuentra en el listado 3.21.

Código 3.21 Constructores de la clase Manecillas Manecilla

1800 public M a n e c i l l a ( i n t l i m i t e ){
1900 LIMITE = l i m i t e ;
2000 /∗ v a l o r e m p i e z a en 0 , p o r s e r p r i m i t i v o ∗/
2100 }
...
2800 M a n e c i l l a ( i n t lim , i n t v a l ) {
2900 LIMITE = l i m ; // Rango p a r a l a m a n e c i l l a
3000 valor = val ; // p o s i c i ó n i n i c i a l i n i c i a l
3100 } // f i r m a : M a n e c i l l a ( i n t , i n t )

La implementación de los constructores para la clase Reloj, que requieren de


la construcción de objetos, se pueden ver en el listado 3.22 en la página opuesta.
101 Clases y objetos

Código 3.22 Constructores de la clase Reloj Reloj

1700 p u b l i c R e l o j ( i n t limH , i n t limM ) {


1800 h o r a s = new M a n e c i l l a ( limH ) ; // P r i m e r p a r á m e t r o
1900 m i n u t o s = new M a n e c i l l a ( limM ) ; // Segundo p a r á m e t r o
2000 } // R e l o j ( i n t , i n t )
...

3000 p u b l i c R e l o j ( i n t limH , i n t limM , i n t h r s , i n t mins ) {


3100 h o r a s = new M a n e c i l l a ( limH , h r s ) ;
3200 m i n u t o s = new M a n e c i l l a ( limM , mins ) ;
3300 } // R e l o j ( i n t , i n t , i n t , i n t )

Podemos seguir con la implementación del resto de los métodos de la clase


Manecilla, que son los más sencillos. El nombre de los métodos indica qué es
lo que se tiene que hacer, por lo que obviaremos los comentarios para aligerar
los listados8 , excepto cuando valga la pena aclarar algo. Para la implementación
de estos métodos utilizaremos ampliamente expresiones aritméticas, para poder
colocarlas del lado derecho de una asignación. Por ello, conviene primero revisar
cómo son las expresiones aritméticas en Java.

3.4 Expresiones en Java

Una expresión en Java es cualquier enunciado que nos regresa (calcula) un


valor. Por ejemplo, new Manecilla(limH) es una expresión, puesto que nos regresa
el lugar (dirección, referencia) donde el sistema construyó a un objeto de la clase
Reloj. Podemos clasificar a las expresiones de acuerdo al tipo del valor que regresen.
Si calculan un valor numérico entonces tenemos una expresión aritmética; si como
resultado del cálculo dan falso o verdadero tenemos una expresión booleana; si
calculan o construyen una cadena de caracteres tenemos una expresión tipo String
–de cadenas–. También podemos hacer que las expresiones calculen (construyan)
un objeto de determinada clase.
Cuando escribimos con papel y lápiz una expresión aritmética tenemos, en
general, dos dimensiones en las cuales movernos: una vertical y otra horizontal.

8
Cuando se introdujeron los encabezados, tanto en las interfaces como en las clases, se co-
mentaron con JavaDoc, por lo que sugerimos consulten esos listados de ser necesario.
3.4 Expresiones en Java 102

Por ejemplo, en la fórmula que da la solución de la ecuación de segundo grado


?2
x1  b b
2a
 4ac
estamos utilizando tres niveles verticales para indicar quién es el dividendo y
quién el divisor. También, para indicar potencia simplemente elevamos un poco el
número 2, e indicamos que la raı́z se refiere a b2  4ac extendiendo la “casita” a
que cubra la expresión.
Cuando escribimos una expresión para un programa de computadora no con-
tamos con estos niveles, sino que tenemos que poner en un solo nivel –en una única
lı́nea– la expresión: si bien vamos a usar, en general, notación infija, debemos es-
cribir la expresión de tal manera que todo se encuentre en la misma lı́nea, pero
manteniendo la aridad, asociatividad y precedencia de la expresión matemática.
La aridad nos habla del número de operandos y, por lo tanto, de a quién afecta
un operador dado. La asociatividad nos dice, si tenemos la presencia de un mismo
operador de manera consecutiva, por ejemplo en a b c d, en qué orden se van
evaluando las sumas9 , mientras que la precedencia se refiere al orden en que se tie-
nen que evaluar las subexpresiones con operadores distintos. Cada operador tiene
una precedencia y asociatividad, pero se pueden alterar éstas usando paréntesis.
Los paréntesis cumplen dos propósitos:

Agrupan subexpresiones, de tal manera que se asocien a un operador. Por


ejemplo, para indicar que el operando de la raı́z es b2  4ac encerrarı́amos
esta subexpresión entre paréntesis.
Cambian el orden en que se evalúan las subexpresiones, ya que en presencia
de paréntesis las expresiones se evalúan de adentro hacia afuera. Por ejemplo:

x{px 1q
x
x 1

x {x
x
1 1
x
Como se puede deducir del ejemplo anterior, la división tiene mayor prece-
dencia (se hace antes) que la suma, por lo que en ausencia de paréntesis se
evalúa como en el segundo ejemplo. Con los paréntesis estamos obligando a
que primero se evalúe la suma, para que pase a formar el segundo operando
de la división, como se muestra en el primer ejemplo.
9
En los lenguajes de programación, como al evaluar una expresión podemos cambiar el valor
de alguno de los operandos, no siempre vamos a tener asociatividad como en matemáticas.
También tenemos el problema de los redondeos donde el resultado depende del orden en que se
hagan.
103 Clases y objetos

Otra diferencia fuerte entre escribir fórmulas o expresiones con papel y lápiz,
y escribirlas en un programa, es que la multiplicación siempre debe ser explı́cita
en el programa:

4ac debe escribirse 4ac


3 px 2y q debe escribirse 3  px 2  y q

Finalmente, son pocos los lenguajes de programación que tienen como operador la
exponencial, por lo que expresiones como b2 se tendrán que expresar en términos
de la multiplicación de b por sı́ misma, o bien usar alguna función (en Java a
las funciones se les llama métodos) como el que usamos para raı́z cuadrada, que
proporcione el lenguaje o alguna de sus bibliotecas. La “famosa” fórmula para la
?2
solución de una ecuación de segundo grado quedarı́a entonces

x1 
 b b  4ac
x1  pb M ath.sqrtppb  bq  p4  a  cqqq{p2  aq
2a
Con esta organización de paréntesis, lo primero que se hace es calcular b  b y
4  a  c. Una vez que se tiene el resultado, se resta el segundo del primero. Una
vez que se tiene el resultado, se le saca raı́z cuadrada (se invoca a un método que
sabe calcularla). Después se resta este resultado de b, se obtiene el producto de
2  a y lo último que se hace es la división. Si no usáramos paréntesis –excepto
por los que tienen que aparecer si se usa alguna función para que no dé un error
de sintaxis–, la expresión se interpretarı́a ası́:
?2
b b  4ac a b M ath.sqrtpb  b  4  a  cq{2  a
2
Sin embargo en la primera expresión no todos los paréntesis son necesarios. Po-
demos eliminar a muchos de ellos, dejando que la precedencia de los operadores
funcione; de se ası́, serı́a suficiente con escribir
?2
x1  b b
2a
 4ac x1  pb M ath.sqrtpb  b  4  a  cqq{p2  aq,

eliminando los paréntesis alrededor de b  b y 4  a  c, ya que la multiplicación


tiene mayor precedencia que la resta.
Otro aspecto importante de los operadores es el número de operandos sobre el
que trabajan. Estamos acostumbrados a operadores unarios (de un solo operando,
como el  o el ) y binarios (como la suma o la multiplicación). En general,
podemos tener operadores que tengan más de dos operandos.
A continuación damos una lista de operadores (no incluye métodos de la cla-
se Math), listados en orden de precedencia y con su asociatividad y número de
3.4 Expresiones en Java 104

operandos indicado. En general los operadores se evalúan de izquierda a derecha,


para operadores de la misma precedencia o iguales (cuando la sintaxis lo permite),
excepto los operadores de asignación que se evalúan de derecha a izquierda. En el
caso de estos operadores únicamente la última expresión a la derecha puede ser
algo que no sea una variable.

Tabla 3.3 Operadores de Java 1/2

Operandos Sı́mbolo Descripción Prec


posfijo unario rs arreglos 1
posfijo unario  selector de clase
prefijo n-ario pxparámetrosyq lista de parámetros
posfijo unario xvariabley auto post-incremento
posfijo unario xvariabley auto post-decremento
unario prefijo xvariabley auto pre-incremento 2
unario prefijo xvariabley auto pre-decremento
unario prefijo xexpresióny signo positivo
unario prefijo xexpresióny signo negativo
unario prefijo xexpresióny complemento en bits 2
unario prefijo ! xexpresióny negación booleana

unario prefijo new xconstructory instanciador 3


unario prefijo pxtipoyq xexpresióny casting
binario infijo  multiplicación 4
binario infijo { división
binario infijo % módulo
binario infijo suma 5
binario infijo  resta
binario infijo    corrimiento de bits a la 6
izquierda llenando con ceros
binario infijo ¡¡ corrimiento de bits a la
derecha propagando el signo
binario infijo ¡¡¡ corrimiento de bits a la
derecha llenando con cero
binario infijo   relacional “menor que” 7
binario infijo   relacional “menor o igual
que”
binario infijo ¡ relacional “mayor que”
(continúa en la siguiente página)
105 Clases y objetos

Tabla 3.3 Operadores de Java 2/2


(Continúa de la página anterior)
Operandos Sı́mbolo Descripción Prec
binario infijo ¡ relacional “mayor o igual
que”
binario infijo instanceof relacional “ejemplar de”
binario infijo  relacional, igual a 8
binario infijo ! relacional, distinto de
binario infijo & AND de bits 9
binario infijo ^ XOR de bits 10
binario infijo | OR de bits 11
binario infijo && AND lógico 12
binario infijo || OR lógico 13
ternario infijo xexp logy?xexpy :xexpy Condicional aritmética 14
binario infijo  asignación 15
binario infijo  auto suma y asignación
binario infijo  auto resta y asignación
binario infijo  auto producto y asignación
binario infijo { auto división y asignación
binario infijo % auto módulo y asignación
binario infijo ¡¡ auto corrimiento derecho 15
con propagación y
asignación
binario infijo    auto corrimiento izquierdo y
asignación
binario infijo ¡¡¡ auto corrimiento derecho
llenando con ceros y
asignación
binario infijo & auto-AND de bits y
asignación
binario infijo ^ auto-XOR de bits y
asignación
binario infijo | auto-OR de bits y
asignación

Sabemos que ésta es una lista extensı́sima de operadores. Conforme vayamos


entrando a cada uno de los temas y requiramos de los operadores, aclararemos
más su uso y su significado.
3.4 Expresiones en Java 106

Estamos ya en condiciones de escribir prácticamente todas las implementacio-


nes de los métodos en nuestro programa. Lo haremos, siguiendo el mismo orden
que utilizamos para escribir los encabezados.

Implementación de los constructores


Lo único que deseamos hacer en los constructores de las clases Manecilla y
Reloj es la de asignar valores iniciales a los atributos, por lo que los revisamos
en el apartado relacionado con la asignación de valores y se encuentran en los
listados 3.21 y 3.22 en la página 101. Hay que recordar que estos métodos, por ser
constructores, de hecho regresan un objeto de la clase de la que son constructores.

Implementación de los métodos de acceso


Los métodos de acceso, como ya mencionamos, regresan un valor del tipo del
atributo que deseamos observar. Se encuentran implementados en los listados 3.20
en la página 98. Los métodos de acceso de la clase Reloj se encuentran en el
listado 3.23.

Código 3.23 Métodos de acceso de la clase Reloj Reloj

8000 public Manecilla getHoras () {


8100 return horas ;
8200 }
...
8800 public Manecilla getMinutos () {
8900 return minutos ;
9000 }

Implementación de los métodos de manipulación


Estos métodos corresponden a todos aquellos que cambian el estado de los
objetos. Los que corresponden a la clase Reloj se encuentran en el listado 3.24 y
las de la clase Manecilla en el listado 3.26 en la página opuesta.

Código 3.24 Métodos de manipulación de la clase Reloj Reloj (1/2)

6000 public void incrementa ( ) {


6100 horas . s e t V a l o r ( horas . getValor () + minutos . incrementa ( ) ) ;
6200 } // f i r m a : R e l o j . i n c r e m e n t a ( )
107 Clases y objetos

Código 3.25 Métodos de manipulación de la clase Reloj Reloj (2/2)

71 p u b l i c v o i d s e t V a l o r ( i n t nvoHoras , i n t nvoMins ) {
72 h o r a s . s e t V a l o r ( n v o H o r a s ) ; // p i d e a h o r a s que cambie
73 m i n u t o s . s e t V a l o r ( nvoMins ) ; // p i d e a s m i n u t o s que cambie
74 } // f i r m a : R e l o j . s e t V a l o r ( i n t , i n t )

En el caso de la clase Manecilla, el trabajo de estos dos métodos es más com-


plicado, pues debe vigilar que la posición de las manecillas sea la correcta (entre
0 y LIMITE-1). En el caso del método incrementa además debe avisar si dio o
no una vuelta completa. La implementación de los métodos se encuentra en el
listado 3.26.

Código 3.26 Métodos de manipulación de la clase Manecilla Manecilla

4000 public int incrementa () {


4100 v a l o r ++;
4200 i n t d i o V u e l t a = v a l o r / LIMITE ; // V e r i f i c a r s i d i o l a v u e l t a
4300 v a l o r %= LIMITE ; // R e g r e s a r l a a l r a n g o
4400 // [ 0 . . LIMITE  1]
4500 return dioVuelta ; // A v i s a r s i d i o l a v u e l t a
4600 } // f i r m a : M a n e c i l l a . i n c r e m e n t a ( )
...
5000 public void s e t V a l o r ( i n t nvoValor ) {
5100 v a l o r = n v o V a l o r % LIMITE ; // V i g i l a r que e s t é
5200 // en r a n g o [ 0 . . LIMITE  1]
5300 } // f i r m a : M a n e c i l l a . s e t V a l o r ( i n t )

Llenado de los métodos de implementación


Los métodos de manipulación, como su nombre lo indica, manipulan, convier-
ten, combinan el valor de uno o más atributos para proporcionar al usuario una
respuesta o realizar una acción, sin cambiar el estado del objeto. El método mues-
tra de la clase Manecilla es de este tipo y únicamente tiene que convertir a cadena
(String) el valor numérico que tiene en su atributo valor. La implementación del
método se encuentra en el listado 3.27.

Código 3.27 Métodos de implementación de la clase Manecilla Manecilla

7700 public S t r i n g muestra () {


7800 r e t u r n "" + v a l o r ; // Para o b l i g a r a c o n v e r t i r a c a d e n a
7900 } // f i r m a : M a n e c i l l a . m u e s t r a ( )
3.4 Expresiones en Java 108

Por último tenemos el método que muestra el reloj en la pantalla y que co-
rresponde al método muestra de la clase Reloj. Como ya mencionamos antes, este
método debe combinar los valores de sus manecillas en una cadena e imprimirla
en la pantalla.
Ya vimos que todo programa de Java proporciona al archivo System.out para
salida sin que lo tengamos que declarar. Ası́ que lo podemos usar directamente en
cualquier momento. Vamos a utilizar, por el momento, únicamente dos métodos
de la clase System.out, el que escribe en la consola y el que al terminar de escribir
salta al principio del siguiente renglón. Sus firmas se encuentran a continuación:
System . o u t . p r i n t ( S t r i n g ) // E s c r i b e en l a t e r m i n a l l a c a d e n a dada
System . o u t . p r i n t l n ( S t r i n g ) // E s c r i b e l a c a d e n a en l a t e r m i n a l y
// da un s a l t o de lı́ n e a .

Con el uso de un dispositivo de salida, la implementación de los métodos se


puede hacer para que escriban directo a la consola. El código de este método se
encuentra en el listado 3.28.

Código 3.28 Métodos de implementación de la clase Reloj Reloj

3900 public void muestra ( ) {


4000 // v a r i a b l e s a u x i l i a r e s p a r a m o s t r a r a l u s u a r i o
4100 S t r i n g mensaje1 , mensaje2 , m e n s a j e 3 ;
3830 m e n s a j e 1 = "Son las " ; // Para i n t e r c a l a r con
3840 m e n s a j e 2 = " horas con " ; // l o s v a l o r e s
3850 m e n s a j e 3 = " minutos " ;
3860 /∗ Se u s a System . o u t p a r a i m p r i m i r d i r e c t o en p a n t a l l a
3870 y s e u s a e l método p r i n t l n que t e r m i n a l a l i n e a
3880 con un cambio de r e n g l ó n
3890 ∗/
3900 System . o u t . p r i n t l n ( m e n s a j e 1 + h o r a s . g e t V a l o r ( )
3910 + mensaje2 + minutos . g e t V a l o r ( )
3920 + mensaje3 ) ;
3930 /∗ E l o p e r a d o r + cuando t r a b a j a con c a d e n a s o b l i g a
3940 a l o s números a c o n v e r t i r s e en c a d e n a y l a s pega .
3950 ∗/
3960 } // f i r m a : m u e s t r a ( )

Uso de las clases definidas


Tenemos ya las clases terminadas. Ahora tendrı́amos que tener un usuario que
“comprara” uno de nuestros relojes. Hagamos una clase cuya única función sea
probar el Reloj. La llamaremos UsoReloj. Se encuentra en el listado 3.29 en la
página opuesta.
109 Clases y objetos

Código 3.29 Clase usuaria de la clase Reloj UsoReloj

100 package R e l o j ;
200 /∗ ∗
300 ∗ C l a s s <code>U s o R e l o j </code> p a r a p r o b a r e l f u n c i o n a m i e n t o de l a s
400 ∗ c l a s e s <code>R e l o j </code> y <code>M a n e c i l l a </code >.
500 ∗
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a v i s o @ g m a i l . com”> E l i s a V i s o </a>
700 ∗ @version 1.0
800 ∗/
900 public class UsoReloj {
1000 /∗ Únicamente usamos un método main , p o r q u e s o l o l a vamos a u s a r
1100 p a r a p r o b a r ( i n v o c a r ) l o s métodos de l a s c l a s e s R e l o j y
1200 Manecilla .
1300 ∗/
1400 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
1500 /∗ D e c l a r a c i o n e s l o c a l e s a main :
1600 /∗ d e c l a r a c i ó n de una v a r i a b l e t i p o R e l o j ∗/
1700 Reloj r e l o j i t o ;
1800 /∗ E l r e l o j s e m o s t r a r á en System . o u t ∗/
1900
2000 /∗ C o n s t r u c c i ó n de l o s o b j e t o s :
2100 V a l o r e s i n i c i a l e s ∗/
2200 r e l o j i t o = new R e l o j ( 1 2 , 6 0 , 1 1 , 5 8 ) ;
2300 /∗ M a n i p u l a c i ó n d e l r e l o j i t o ∗/
2400 r e l o j i t o . incrementa ( ) ;
2500 r e l o j i t o . muestra ( ) ;
2600 r e l o j i t o . incrementa ( ) ;
2700 r e l o j i t o . muestra ( ) ;
2800 r e l o j i t o . setValor (10 ,59);
2900 r e l o j i t o . muestra ( ) ;
3000 r e l o j i t o . incrementa ( ) ;
3100 r e l o j i t o . muestra ( ) ;
3200 } // main
3300 } // U s o R e l o j

Se estarán preguntando por qué no se declaró a relojito como atributo de la


clase. La razón es que un método estático de la clase no puede tener acceso a
atributos de esa clase. Por ello hay que declararlo en el método. De cualquier
forma, como la clase Reloj es pública, cualquiera puede pedir constructores de esa
clase.
La ejecución de la aplicación anterior, junto con la lı́nea de comandos, se
encuentra en la figura 3.21. Noten que como estamos trabajando con el paquete
Reloj, las clases correspondientes van a ser buscadas en el subdirectorio Reloj, por
lo que es necesario invocar desde el subdirectorio superior, en el que se encuentra
3.4 Expresiones en Java 110

el paquete Reloj.

Figura 3.21 Ejecución de la aplicación UsoReloj


e l i s a @ l a m b d a : ˜ / ICC1 / p r o g r a m a s $ j a v a R e l o j / U s o R e l o j
Son l a s 11 h o r a s con 59 m i n u t o s
Son l a s 0 h o r a s con 0 m i n u t o s
Son l a s 10 h o r a s con 59 m i n u t o s
Son l a s 11 h o r a s con 0 m i n u t o s

La primera lı́nea es la invocación de la aplicación, llamando a la clase que


tiene un método main, UsoReloj. La segunda lı́nea es la ejecución de la lı́nea 2500,
después de incrementar un reloj que inició a las 11 horas con 58 minutos. La
segunda lı́nea resulta de la ejecución de la lı́nea 2700, que resulta de incrementar
el reloj y hacer que ambas manecillas den la vuelta completa. La tercera lı́nea
corresponde a ejecutar la lı́nea 2900 después de ponerle una hora nueva al reloj
de las 10 horas con 59 minutos en la lı́nea 2800 del método main. La última lı́nea
de la ejecución corresponde a incrementar el reloj, lo que hace que la manecilla
de minutos complete una vuelta e incremente a la manecilla de las horas.

3.4.1. Redireccionamiento de la salida de la aplicación

Como ya sabrán, cuando se le pide a la máquina virtual de Java que ejecute


la aplicación desde la consola, que en este caso es con
e l i s a @ l a m b d a : ˜ / ICC1 / p r o g r a m a s }$ j a v a Reloj / UsoReloj

siempre se puede redirigir la salida que va al dispositivo estándar –que es, preci-
samente, la consola asignada a System.out–. Esta redirección puede ser para crear
un archivo nuevo, usando el sı́mbolo ¡ y a continuación el nombre de un archivo;
si el archivo ya existe, lo sustituye por el que se está creando, mientras que si el
archivo no existe todavı́a simplemente crea uno nuevo. También se puede redirigir
para agregar al final de un archivo que ya existe, usando ¡¡ –dos ¡ juntos–,
seguidos por el nombre de un archivo; si el archivo ya existe, agrega al final de lo
que ya haya en el mismo; si no existe, simplemente lo crea nuevo y contiene única-
mente lo que acabamos de escribir. Por ejemplo, si deseamos guardar la salida de
la aplicación en un archivo que se llame usoReloj . txt , invocarı́amos la aplicación
de la siguiente manera:
e l i s a @ l a m b d a : ˜ / ICC1 / p r o g r a m a s $ j a v a R e l o j / U s o R e l o j ¡ usoReloj . txt
111 Clases y objetos

Eso crearı́a el archivo usoReloj . txt y colocarı́a ahı́ todo lo escrito en System.out. Si
el comando fuera
e l i s a @ l a m b d a : ˜ / ICC1 / p r o g r a m a s $ j a v a R e l o j / U s o R e l o j ¡¡ usoReloj . txt

agregarı́a al final del archivo creado en alguna otra ejecución o a través del editor
favorito.

3.5 Declaración y definición simultáneas

No hemos mencionado que en Java se permite asignar valor inicial a los atri-
butos y a las variables locales en el momento en que se declaran. Esto se consigue
simplemente con el operador de asignación y una expresión:
public int valor = 0;
R e l o j r e l o j i t o = new R e l o j ( 1 2 , 6 0 ) ;

Para el caso de los atributos de un objeto, no se le ve mucho caso asignar


estado inicial a los atributos, excepto cuando queramos que todos los objetos de
esa clase compartan el estado inicial. Por ejemplo, en el caso de los objetos de la
clase Reloj es posible que queramos que todos los objetos empiecen con las 0 horas
y 0 minutos; pero en el caso de los objetos de la clase Manecilla, si le diéramos
valor inicial al atributo LIMITE después ya no podrı́amos volverle a asignar un
valor pues está declarada como constante, y todos los objetos tendrı́an el mismo
lı́mite, algo que no queremos que suceda.
Es necesario enfatizar que las constantes (con el modificador final) toman un
valor una única vez. En general este valor se les asigna cuando se declaran, pero
como pudimos ver en el caso de la clase Manecilla, cada objeto debe trabajar con
una constante distinta, no es la misma para todos los objetos de la clase. Java
permite para estos casos asignar valor a una constante en el constructor; ninguna
otra clase de método aceptará la asignación de un valor a una variable declarada
con el modificador final, pues no puede vigilar que esto se haga sólo una vez. En
cambio, con los constructores, una vez construido el objeto no se puede volver a
construir ese mismo objeto.
Todos los listados completos de este capı́tulo los pueden encontrar en

http://lambda.fciencias.unam.mx/icc1
3. Ejercicios 112

Ejercicios

3.1.- En la siguiente especificación de un problema, identifica los objetos y los


métodos.

La profesora dejó una tarea de aritmética que consiste de 10 mul-


tiplicaciones largas. Los estudiantes tienen que resolver la tarea y
la profesora deberá calificarla.

3.2.- En la siguiente especificación de un problema, identifica los objetos y los


métodos.

Tenemos una cena que consiste de tres tiempos. El primero es una


ensalada, el segundo tiempo es una sopa y el tercer tiempo es un
plato de carne o pescado. El comensal puede elegir si es ensalada
fresca o de pasta; si es sopa o crema de verduras; y si es carne o
pescado. La cena deberá prepararla el cocinero.

3.3.- Elabora las tarjetas de responsabilidades para la maestra del problema 1

3.4.- Elabora las tarjetas de responsabilidades para el comensal del problema 2.

3.5.- Si pensamos en la cena como una clase, ¿cuáles son los atributos de esta
clase? (dado que el segundo y el tercer tiempo hay que elegir entre dos cosas,
podemos representarlas con variables booleanas –sı́ o no– o, previniendo más
opciones, con un entero).

3.6.- Escribe el encabezado en Java de un método de acceso que nos diga si el


segundo tiempo es sopa o crema.

3.7.- Escribe el encabezado de un método mutante que elija si la cena va a ser


con sopa o crema.

3.8.- Escribe en Java el método de implementación al que le damos como paráme-


tros el tipo del segundo tiempo y del tercer tiempo y los actualiza.

3.9.- Escribe en Java el constructor de la clase cena.

3.10.- Modifica la clase Reloj para que tenga una manecilla para los segundos y
para décimas de segundo (pruébalo en la computadora).
113 Clases y objetos

3.11.- Modifica la clase Reloj para que sea un reloj de 24 horas y marque si es AM
o PM (pruébalo en la computadora).

3.12.- Tenemos un registro en una agenda telefónica con el nombre, dirección


teléfono fijo, posiblemente extensión, teléfono celular y dı́a de cumpleaños.
Escribe la clase en Java que represente a este registro, con sus métodos de
acceso, mutantes, constructores y que muestre el registro completo.

3.13.- Tenemos el registro bibliográfico de un libro. Escribe una clase en Java que
lo represente.

3.14.- Tenemos el registro de un estudiante en la sección escolar, que tiene su


nombre, número de cuenta, fecha de nacimiento y clave de la carrera. Escribe
la clase en Java que represente a un estudiante con estos datos.

3.15.- Tenemos un cañón que puede ser utilizado con la computadora. Tiene los
botones de encendido, foco, distancia y enfriamiento y responde acorde a
cada uno de estos botones. Escribe en Java una clase que represente a un
cañón como se describió.
Manejo de cadenas
y expresiones 4
Uno de los ingredientes que más comúnmente vamos a usar en nuestros progra-
mas son las expresiones. Por ello dedicaremos este capı́tulo a ellas. Sin embargo,
antes de entrar en materia respecto a lo que sucede “dentro” de la aplicación
acerca de cómo combinar los elementos con los que trabajamos, deseamos poner
un poco de atención a la manera que podemos tener de colocar valores en esos
elementos. Por lo pronto únicamente contamos con dos mecanismos: la asignación
y la construcción de objetos (ejemplares) de una clase. Vamos a explorar cómo
podemos asignar valores que el usuario proporciona. Por lo pronto lo haremos úni-
camente a través de la consola, pues, como ya mencionamos, el manejo de entrada
y salida de Java no es para principiantes.

4.1 Lectura de datos primitivos

En el capı́tulo anterior vimos cómo escribir en la pantalla y, usando redireccio-


namiento en la invocación de la aplicación, cómo hacerlo en un archivo en disco.
Veremos ahora cómo leer datos primitivos y cadenas desde la pantalla.
4.1 Lectura de datos primitivos 116

Java proporciona un dispositivo (objeto) para la lectura de caracteres, que se


encuentra en la clase System y se llama in y que al igual que System.out, existe en
cuanto se empieza a ejecutar cualquier aplicación, por lo que no hay que declararlo
ni definirlo. Sin embargo, como la lectura es siempre peligrosa, pues descansa en las
acciones de un usuario, todo lo que deseamos realizar en el dispositivo asociado
a System.in –la consola– tiene que ser vigilado sintácticamente en la aplicación
mediante el manejo de excepciones, algo que veremos cómo hacer y qué significa
hacia la última cuarta parte del material1 . Esto dificulta el uso directo de System.in
en nuestras aplicaciones.
Por lo pronto nos preocuparemos por leer únicamente datos primitivos y ca-
denas. Para ello Java proporciona una clase, Scanner, que permite la lectura de
datos primitivos y que, si se coloca sobre System.in, no requiere de vigilancia –si
hay un error en la ejecución, el programa simplemente abortará–.
La clase Scanner se encuentra en el paquete java . util y se tiene que avisar en
la aplicación que se desea usar y que la ponga disponible. Esto se hace con el
comando import, cuya sintaxis se encuentra en la figura 4.1.

Figura 4.1 Importación de clases o paquetes


Sintaxis:
x aviso de importacióny ::= import xlista de paquetes o clasesy ;
xlista de paquetes o clasesy::=xpaquete o clasey,
xlista de paquetes o clasesy
| xpaquete o clasey
Semántica:
Un xpaquete o clasey es un identificador que ubica a una clase particular en
un directorio de clases de Java. Si se trata de un paquete completo, como
el que pudiese ser el de entrada y salida, escribirı́amos import java. util .∗;
indicando que queremos tener disponibles todas las clases del paquete []util
que se encuentra en el subdirectorio de clases de Java. El ∗ indica que queremos
todas las clases de ese paquete. Si sólo queremos importar una sola clase, como
en el caso de Scanner, ubicamos en qué paquete se encuentra y pedimos la
importación únicamente de esa clase con import java. util .Scanner;. Podemos
pedir más de una importación, separando a los identificadores de los paquetes o
clases con una coma y terminado la especificación con un punto y coma. Pueden
aparecer tantos enunciados de import como queramos.

1
La mayorı́a de los métodos de la mayorı́a de las clases que realizan entrada y salida lanzan
lo que se conoce como excepciones, que hay que vigilar y prever. Revisaremos este tema en el
capı́tulo correspondiente a errores en ejecución.
117 Manejo de cadenas y expresiones

Los comandos import deben aparecer antes de la declaración de la clase, pero


después de la especificación del paquete al que pertenece la clase.
Veremos únicamente algunos de los métodos de la clase Scanner, pues muchos de
ellos requieren conocimientos más extensos del lenguaje que todavı́a no poseemos.
Un revisor (scanner ) simple de texto es un dispositivo que puede revisar tipos
primitivos y cadenas usando expresiones regulares.
Un Scanner divide su entrada en átomos usando un patrón delimitador, que por
omisión corresponde a un blanco, tabulador o cambio de lı́nea. El átomo resultante
se convierte a un valor que puede ser de distintos tipos usando las variaciones de
los métodos next.
Por ejemplo, el siguiente código permite leer un número desde System.in:
S c a n n e r s c = new S c a n n e r ( System . i n ) ;
int i = sc . nextInt ( ) ;

En este código, “montamos” al scanner en el dispositivo System.in para leer de


la consola –construimos un objeto tipo Scanner, inicializado sobre System.in–.
Si deseamos leer de un archivo en disco basta con redireccionar la entrada en la
invocación de la aplicación, usando el sı́mbolo   seguido del nombre del archivo,
a continuación de la invocación de la aplicación con la máquina virtual de Java:
e l i s a @ v i s o : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a UsoCadenas   par
aCadenas . t x t

En la invocación anterior estamos solicitando a la máquina virtual de Java que lea


sus datos (dirigidos al dispositivo estándar de la computadora, el teclado) desde
el archivo en disco llamado “paraCadenas.txt” y que se encuentra en el mismo
subdirectorio desde el que se está invocando a la aplicación.
Una operación del scanner puede bloquearse2 esperando que se teclee algo.
Los métodos next() , hasNext() y sus variantes para leer tipos primitivos, (tales
como nextInt () y hasNextInt() ) primero saltan cualquier entrada que cace con el
delimitador y después intentan devolver el siguiente átomo, que termina cuando
se encuentran nuevamente al delimitador (o sucesiones de éste). Ambos métodos
hasNext y next pueden bloquearse esperando entrada. El que un hasNext se bloquee
no tiene relación con que su next asociado se vaya a bloquear.
Un scanner puede leer texto en cualquier objeto que implemente la interfaz
Readable y se traga los errores, suponiendo que se alcanzó el fin del archivo. Tam-
bién puede interpretar cadenas ( String ).
Al cerrar a un Scanner, cerrará su fuente de entrada si es que la fuente de
entrada tiene implementada la interfaz Closeable .
2
Cuando se bloquea un dispositivo la aplicación “se congela” y no sucede nada hasta que el
dispositivo se desbloquea o la aplicación se aborta.
4.1 Lectura de datos primitivos 118

A menos que se especifique, si se pasa un parámetro nulo a cualquier método


de Scanner, habrá un error de NullPointerException y el programa terminará.
Un scanner, por omisión, espera la representación de números en decimal, a
menos que se establezca otra base con el método Radix(int). El método reset () res-
tablecerá el valor del scanner a base 10, aunque haya sido cambiada previamente.
Para ver la sintaxis particular de cada dato primitivo, ésta se encuentra en la
descripción de la clase en la página correspondiente de Java, denotada con BNF.
Al leer datos primitivos los espacios no son relevantes: uno o más espacios (o
tabuladores o fines de lı́nea) funcionan para separar a los datos primitivos. En
el caso de las cadenas, sin embargo, que no son datos primitivos, todo lo que se
encuentra a partir de que se le pide que lea una cadena y hasta que encuentre el
primer fin de lı́nea forma parte de la cadena.
Al leer un dato primitivo, éste termina con el primer separador pero no lo
“consume”. Esto quiere decir que el siguiente dato empezará a buscarse a partir
del último separador encontrado inclusive. Por lo tanto, si leemos un número, que
se encuentra en una sola lı́nea y termina con un fin de lı́nea, y a continuación
leemos una cadena, ésta no contendrá ningún carácter, pues se encontrará inme-
diatamente con el fin de lı́nea.
La clase Scanner se encuentra disponible a partir de la versión 1.5 de Java.
En la tabla 4.1 se encuentran los métodos que vamos a usar.

Tabla 4.1 Métodos y atributos de Scanner (1/4)

Class java.util.Scanner
Dispositivo para lectura simple de datos primitivos y cadenas.
Constructores:
Scanner(InputStream source)
Construye un nuevo Scanner que produce valores revisados
en el flujo de entrada especificado.
Scanner(String source)
Construye un nuevo Scanner que produce valores revisados
de la cadena especificada.
Métodos:
void close ()
Cierra este scanner.
Pattern delimiter ()
Regresa el patrón que está usando el scanner para empa-
tar delimitadores.
(Continúa en la página siguiente)
119 Manejo de cadenas y expresiones

Tabla 4.1 Métodos y atributos de Scanner (Viene de la página anterior)


(2/4)

boolean hasNext()
Regresa verdadero si este scanner tiene otro átomo en su
entrada.
boolean hasNextBoolean()
Regresa verdadero si el siguiente átomo en la entrada de
este scanner puede ser interpretado como un valor boo-
leano, usando un patrón independiente de mayúsculas o
minúsculas creado de alguna de las cadenas true | false.
boolean hasNextByte()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor de byte en la base
por omisión (base 10 entre -128 y 127).
boolean hasNextByte(int radix )
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor de byte en la base
indicada en radix.
boolean hasNextDouble()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor double.
boolean hasNextFloat()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor float.
boolean hasNextInt()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor int en la base por
omisión.
boolean hasNextInt(int radix )
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor int en la base espe-
cificada en radix.
boolean hasNextLine()
Regresa verdadero si hay una lı́nea más por revisar en
este scanner.
boolean hasNextLong()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor long.
(Continúa en la página siguiente)
4.1 Lectura de datos primitivos 120

Tabla 4.1 Métodos y atributos de Scanner (Continúa de la página anterior)


(3/4)

boolean hasNextLong(int radix)


Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor long en la base es-
pecificada en radix.
boolean hasNextShort()
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor short.
boolean hasNextShort(int radix )
Regresa verdadero si el siguiente átomo en este scanner
puede ser interpretado como un valor short en la base
especificada en radix.
MatchResult match()
Regresa el resultado del apareamiento hecho por la última
operación ejecutada por este scanner.
String next()
Encuentra y regresa el siguiente átomo completo de este
scanner.
boolean nextBoolean()
Revisa y regresa el siguiente átomo interpretando como
un boolean.
byte nextByte()
Revisa y regresa el siguiente átomo interpretando como
un byte.
byte nextByte(int radix )
Revisa y regresa el siguiente átomo interpretando como
un byte en la base indicada por radix.
double nextDouble()
Revisa y regresa el siguiente átomo interpretando como
un double.
float nextFloat ()
Revisa y regresa el siguiente átomo interpretando como
un float.
int nextInt ()
Revisa y regresa el siguiente átomo interpretando como
un int.
int nextInt (int radix )
Revisa y regresa el siguiente átomo interpretando como
un int en la base indicada por radix.
(Continúa en la página siguiente)
121 Manejo de cadenas y expresiones

Tabla 4.1 Métodos y atributos de Scanner (4/4)


(Viene de la página anterior)

String nextLine()
Avanza el scanner a la siguiente lı́nea y regresa lo que se
saltó.
long nextLong()
Revisa y regresa el siguiente átomo interpretando como
un long.
long nextLong(int radix )
Revisa y regresa el siguiente átomo interpretando como
un long en la base indicada como radix.
short nextShort()
Revisa y regresa el siguiente átomo interpretando como
un short.
short nextShort(int radix )
Revisa y regresa el siguiente átomo interpretando como
un short en la base indicada como radix.
int radix ()
Regresa el valor con el que el scanner está trabajando
como base.
Scanner reset ()
Reinicia este scanner.
String toString ()
Regresa la representación en cadena de este scanner.
Scanner useRadix(int radix )
Establece la base numérica para la base especificada.

Supongamos que queremos leer tres enteros de la consola en el método main.


El código se verı́a como se muestra en el listado 4.1, asociando a un objeto de la
clase Scanner con el teclado.

Código 4.1 Ejemplo del uso de un objeto Scanner Pruebas (1/2)

package L e c t u r a ;
import j a v a . u t i l . S c a n n e r ;
public c l a s s Pruebas {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r c o n s = new S c a n n e r ( System . i n ) ;
System . o u t . p r i n t l n ( "Dame un entero , seguido de un real "
+ " y después una cadena " ) ;
4.1 Lectura de datos primitivos 122

Código 4.1 Ejemplo del uso de un objeto Scanner Pruebas (2/2)

i n t i = cons . n e x t I n t ( ) ;
double x = c o n s . n e x t D o u b l e ( ) ;
S t r i n g l i n e a = cons . nextLine ( ) ;
System . o u t . p r i n t l n ( "El entero : " + i + "\tEl real: "
+ x + "\nLa cadena : \""
+ l i n e a + "\"" ) ;
}
}

Compilamos este programa y lo ejecutamos redireccionando tanto la entrada


como la salida. Supongamos que ejecutamos el programa con la siguiente lı́nea de
comando:
e l i s a @ v i s o : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a L e c t u r a . P r u e b a s
  Datos0 . t x t ¡ Salida0 . txt

El archivo de entrada tiene lo siguiente:


3245 3870.23Esta es una cadena ê
ê
Noten que no hay nada entre el último dı́gito del número real que espera y la
cadena. En este caso, como no encuentra un blanco o fin de lı́nea que avise que el
número ya terminó, no acaba de leer el real y aborta con el siguiente mensaje en
la pantalla:
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:840)
at java.util.Scanner.next(Scanner.java:1461)
at java.util.Scanner.nextDouble(Scanner.java:2387)
at Lectura.Pruebas.main(Pruebas.java:8)

En el archivo de salida Salida0 . txt alcanza a escribirse lo siguiente:


Dame un entero, seguido de un real y después una cadena

Si colocamos al menos un blanco entre el número y la cadena, todo sale bien.


Invocamos de la siguiente manera:
e l i s a @ v i s o : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a L e c t u r a . P r u e b a s
<DatosA . t x t > S a l i d a A . t x t

lo que aparece en el archivo de salida es lo siguiente:


123 Manejo de cadenas y expresiones

Dame un entero, seguido de un real y después una cadena


El entero: 3245 El real: 3870.23
La cadena: " Esta es una cadena"

Algunas llamadas de atención


Deseamos insistir que todos los números que van a aparecer en una entrada
deben ir terminados por un blanco, un tabulador o un fin de lı́nea, para que la
máquina virtual sepa que ya terminó ese elemento. Sin embargo, no lo consume,
sino que lo deja como primer elemento de lo siguiente que se quiera leer. Por lo
tanto si a un número sigue una cadena, debe haber al menos un blanco entre el
final del número y la cadena, aunque ese blanco sea tomado como parte de la
cadena.
Si se coloca a un número solo en una lı́nea y se quiere leer a continuación de
él una cadena, se tiene que consumir el fin de lı́nea antes de leer la cadena. Esto
se logra buscando una cadena vacı́a antes de tratar de leer la cadena. Si no se
hace ası́, como el carácter de fin de lı́nea no será consumido con el número, al dar
la lectura de la cadena ésta quedará vacı́a pues procederá a consumir el carácter
de fin de lı́nea que quedó “atorado”. Para leer una cadena vacı́a simplemente se
invoca cad = cons.nextLine() dos veces consecutivas, pero la primera vez no se usa
el resultado.
Conforme vayamos revisando la aplicación, veremos un poco más sobre entrada
y salida.

4.2 Manejo de cadenas en Java

Una expresión es cualquier sucesión de operadores y operandos que producen


(regresan) un valor al evaluarse. El valor puede ser numérico, de cadenas, una
referencia a un objeto, un valor booleano o de cualquier otra clase accesible al
método en el que se encuentra la expresión.
Las cadenas de caracteres van a ser de lo que más vamos a usar en nuestro
desarrollo profesional. Prácticamente todo el manejo que hagamos involucrará ca-
denas, ya sea como tı́tulos, como objeto de búsquedas, de agrupamiento, etcétera.
Las cadenas en Java son una clase que nos proporciona el paquete Java.Lang y
está accesible sin necesidad de importarlo. Cada vez que declaramos una cadena
4.2 Manejo de cadenas en Java 124

mediante el tipo String, reservamos espacio únicamente para la referencia, ya que


se trata de objetos. Sin embargo, por lo común que son las cadenas, la sintaxis de
Java es mucho más flexible para la creación de cadenas que de objetos en general
y nos permite cualquiera de los siguientes formatos:

i. En la declaración. Simplemente inicializamos la variable con una cadena:


S t r i n g c a d e n a = "Esta es una cadenita " ;

ii. En una asignación. Se asigna una cadena a una variable tipo String:
S t r i n g cadenota ;
c a d e n o t a = "Una cadena "+ " muy larga " ;

iii. Al vuelo. Se construye una cadena como una expresión, ya sea directamente
o mediante funciones de cadenas:
" Cadena Muy Larga " . t o L o w e r C a s e ( )

Es importante mencionar que las cadenas, una vez creadas, no pueden ser
modificadas. Si se desea modificar una cadena lo que se debe hacer es construir
una nueva con las modificaciones y, en todo caso, reasignar la nueva. Por ejemplo,
si queremos pasar a mayúsculas una cadena, podrı́amos tener la siguiente sucesión
de enunciados:

1 S t r i n g m i n u s c = "está en minúsculas " ;


2 minusc = minusc . toUpperCase ( ) ;

Nótese que en el primer renglón de este código la cadena contiene únicamente


minúsculas, por lo que se crea en el heap y se deja en minusc la referencia a esta
cadena (lo denotamos en los esquemas como una flecha ya que el contenido de
la variable apunta a lo localidad en el heap donde se crea el objeto; el contenido
de la variable es la dirección en el heap). En el lado derecho de la asignación en
el segundo renglón se construye una cadena nueva que es la cadena minusc pero
pasada a mayúsculas, creándose una nueva cadena en el heap; lo último que se
hace es reasignar la referencia de minusc a que ahora apunte a esta nueva cadena.
Veamos un esquema de qué es lo que pasa en la figura 4.2 en la siguiente página.
125 Manejo de cadenas y expresiones

Figura 4.2 Inmutabilidad de cadenas

Memoria Heap
1: minusc está en minúsculas

2: minusc está en minúsculas

ESTÁ EN MINÚSCULAS

Lo distinto cuando a manejo de cadenas se refiere es que no necesitamos el


operador new –aunque lo podemos usar con alguno de los métodos constructores
–para construir un objeto tipo String–.
La clase String proporciona muchı́simos métodos para trabajar con cadenas.
No mostramos todos, pues algunos de ellos tienen parámetros o entregan valores
de tipos que no hemos visto todavı́a. A continuación se encuentra la tabla 4.2, con
los métodos que podemos querer usar de la clase String.
Tabla 4.2 Métodos y atributos de la clase String (1/4)

Class String
Representa a las cadenas de caracteres.
Constructores
String ()
String ( String )
Construye una nueva cadena, nula en el primer caso y
una copia de la primera en el segundo. En ambos casos
regresa un apuntador al heap.
Métodos para crear nuevas cadenas:
String concat( String )
Crea una nueva cadena que es a la que se le solicita el
método, seguida del argumento.
(Continúa en la siguiente página)
4.2 Manejo de cadenas en Java 126

Tabla 4.2 Métodos y atributos de la clase String (2/4)

(Continúa de la página anterior)

Métodos para crear nuevas cadenas:


String replace (char, char)
Crea una nueva cadena en la que reemplaza las presencias
del primer carácter por el segundo.
String replace ( String , String )
Crea una nueva cadena en la que reemplaza las presencias
de la primera cadena por la segunda.
String substring (int)
String substring (int , int)
Crean una nueva cadena que es una subcadena de la cade-
na. La subcadena empieza en el primer entero y termina,
en el primer caso al final de la cadena y en el segundo en
el segundo entero, pero no lo incluye.
String toLowerCase()
Crea una nueva cadena convirtiendo todos los caracteres
a minúsculas.
String toUpperCase()
Crea una nueva cadena convirtiendo todos los caracteres
a mayúsculas.
String trim()
Crea una nueva cadena quitando los blancos del principio
y final
static String valueOf(boolean)
static String valueOf(char)
static String valueOf(int)
Crea una cadena con el valor que corresponde al tipo del
dato. Como es estática se puede llamar desde la clase:
String .valueOf( valor ) .
static String valueOf(long)
static String valueOf( float )
static String valueOf(double)
Crea una cadena con el valor que corresponde al tipo del
dato. Como es estática se puede llamar desde la clase:
String .valueOf( valor ) .
(Continúa en la siguiente página)
127 Manejo de cadenas y expresiones

Tabla 4.2 Métodos y atributos de la clase String (3/4)

(Continúa de la página anterior)

Métodos de comparación:
int compareTo(String)
Comparan
$ dos cadenas en el orden del código Unicode.
'
' 0 si las cadenas son idénticas
'
'
'
&
¡0 si xcad1y va después en el orden
Regresa
' que xcad2y.
'
'
'
'
%  0 si xcad1y va antes en el orden
que xcad2y.
boolean equals(Object)
Dice si la cadena en el parámetro es idéntica a la que
invoca.
boolean equalsIgnoreCase( String )
Dice si la cadena en el argumento es igual a la que invoca,
ignorando diferencias entre mayúsculas y minúsculas.
Métodos de búsqueda:
boolean endsWith(String)
Dice si la cadena con la que se invoca termina con la
cadena en el parámetro.
int indexOf(int)
int indexOf(int , int)
int indexOf(String )
int indexOf(String , int)
El primer entero corresponde al código de un carácter en
Unicode (se puede pasar como argumentos también un
carácter). La cadena se refiere a una subcadena. En las
cuatro versiones, regresa la primera posición en la cadena
que invoca donde se encuentra el primer parámetro. Si
se da un segundo parámetro, éste indica que se busque a
partir de esa posición. Regresa -1 si no encuentra lo que
está buscando.
boolean startsWith ( String )
boolean startsWith ( String , int)
Determina si es que la cadena empieza con la cadena que
trae como argumento. En la segunda versión, ve a partir
del carácter denotado por el argumento entero.
(Continúa en la siguiente página)
4.2 Manejo de cadenas en Java 128

Tabla 4.2 Métodos y atributos de la clase String (4/4)


(Continúa de la página anterior)

Métodos de búsqueda:
int lastIndexOf (char)
int lastIndexOf (char, int)
int lastIndexOf ( String )
int lastIndexOf ( String , int)
El carácter corresponde a un carácter (se puede pasar co-
mo argumentos también un código entero de un carácter
en Unicode). La cadena se refiere a una subcadena. En las
cuatro versiones regresa la última posición en la cadena
que invoca donde se encuentra el primer parámetro. Si
se da un segundo parámetro, éste indica que se busque a
partir de esa posición. Regresa -1 si no encuentra lo que
está buscando.
boolean regionMatches(int, String , int , int)
boolean regionMatches(boolean, int, String , int , int)
Determina si una región de la cadena que invoca es igual
a una región de la cadena en el argumento. La segunda
versión, si el argumento booleano es verdadero, compara
ignorando diferencias entre mayúsculas y minúsculas. El
primer entero es la posición de la región en la cadena
que invoca; el segundo entero es la posición inicial en la
cadena del argumento. La tercera posición es el número
de caracteres a comparar.
Métodos de conversión
char charAt(int)
Regresa el carácter que se encuentra, en la cadena que
invoca, en la posición dada por el argumento.
String toString ()
Genera la representación en cadena del objeto con el que
se le invoca.
Otros Métodos
int length ()
Regresa el tamaño de la cadena, el número de caracteres.

Supongamos que queremos comparar dos cadenas para ver si son la misma.
Queremos quitar los blancos del principio y final y descontar si una tiene algunas
mayúsculas y la otra no. Para ello aplicamos primero la función que “poda” (trim)
129 Manejo de cadenas y expresiones

y después comparamos convirtiendo ambas a minúsculas. En el listado 4.2 en la


siguiente página se encuentra un ejemplo del uso de métodos de cadenas, junto
con la ejecución del mismo. La lı́nea con la que se invocó es la siguiente:
e l i s a @ v i s o : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a UsoCadenas   p a r a
Cadenas . t x t ¡ UsoCadenas . t x t

En este comando estamos pidiendo que redirija la entrada (de System.in) al


archivo de texto que se llama paraCadenas.txt; respecto a la salida, estamos pidiendo
que lo pegue al final del archivo UsoCadenas.java, que es el de nuestra aplicación.
Los archivos de entrada (figura 4.3), de código (listado 4.2) y de salida (figura 4.4)
se encuentran en ésta y las páginas que siguen.

Figura 4.3 Archivo de entrada para UsoCadenas.java

Cadenas Para c o m p a r a r
c a d e n a s p a r a Comparar

Código 4.2 Uso de cadenas y redireccionamiento de entrada y salida UsoCadenas (1/2)

1 import j a v a . u t i l . S c a n n e r ;
2 /∗ ∗
3 ∗ C l a s e <code>UsoCadenas </code> p a r a e j e m p l i f i c a r e l u s o de a r c h i v o
4 ∗ de e n t r a d a y c a d e n a s .
5 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ v i s o ”> E l i s a V i s o G u r o v i c h </a>
6 ∗ @version 1.2
7 ∗/
8 p u b l i c c l a s s UsoCadenas {
9 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
10 S c a n n e r s I n = new S c a n n e r ( System . i n ) ;
11 S t r i n g s1 = s I n . n e x t L i n e ( ) ;
12 S t r i n g s2 = s I n . n e x t L i n e ( ) ;
13 System . o u t . p r i n t l n ( "/*\n Las cadenas que se leyeron "
14 + "son las siguientes :\n"
15 + " \"" + s 1 + "\"\n"
16 + " \"" + s 2 +"\"\n*/" ) ;
17 boolean i g u a l e s = s 1 . t r i m ( ) . t o L o w e r C a s e ( )
18 . e q u a l s ( s2 . trim ( ) . toLowerCase ( ) ) ;
19 System . o u t . p r i n t l n ( "\n/* Después de las operaciones , "
20 + " s1 es: \n \""
21 + s 1 +"\"\n s2 es :\n \""
22 + s 2 + "\"\n" ) ;
4.2 Manejo de cadenas en Java 130

Código 4.2 Uso de cadenas y redireccionamiento de entrada y salida UsoCadenas (2/2)

23 System . o u t . p r i n t l n ( "\n/* Salida para el programa :"


24 +"\n Las cadenas :\n*"
25 + s 1 + "\"\n \"" + s 2 + "\"\n"
26 + ( i g u a l e s ? " " : " no" )
27 + " contienen el mismo texto "
28 + "\n*/" ) ;
29 } // main
30 } // UsoCadenas

Figura 4.4 Archivo de salida para UsoCadenas.java


/∗
Las c a d e n a s que s e l e y e r o n s o n l a s s i g u i e n t e s :
” Cadenas Para c o m p a r a r ”
” c a d e n a s p a r a Comparar ”
∗/

/∗ Después de l a s o p e r a c i o n e s , s1 es :
” Cadenas Para c o m p a r a r ”
s2 es :
” c a d e n a s p a r a Comparar ”

/∗ S a l i d a p a r a e l programa :
Las c a d e n a s :
∗ Cadenas Para c o m p a r a r ”
” c a d e n a s p a r a Comparar ”
c o n t i e n e n e l mismo t e x t o
∗/

Es frecuente también que queramos obtener algún valor primitivo a partir de


una cadena o que queramos conocer propiedades de los tipos primitivos con que
contamos. Por ejemplo, si tenemos la cadena "127.5", queremos el valor represen-
tado por esta cadena, no la sucesión de dı́gitos que la forman. Para ello podemos
recurrir a las clases que envuelven 3 a los tipos primitivos y que tienen métodos
estáticos (de la clase), entre muchos otros, que sirven para obtener valores pri-
mitivos que corresponden a una cadena y conocer la representación o el valor de
alguna variable de tipo primitivo. Por el momento no mencionaremos todos los
métodos de todas las clases envolventes, sino únicamente los que tienen que ver
con conversión de cadenas a tipos primitivos y aquellos atributos que se refieren a
éstos. Como se trata de métodos estáticos, no se tienen que construir objetos de
la clase para poder usarlos. Se muestran en la tabla 4.3 en la página opuesta.
3
Del inglés wrappers.
131 Manejo de cadenas y expresiones

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (1/9)

Class Boolean
Es una envoltura para el tipo primitivo boolean.
Métodos:
static boolean getBoolean(String name)
Convierte a la cadena ‘‘ true’’ en el valor booleano ver-
dadero y a cualquier otra cadena en falso.
static boolean parseBoolean(String s)
Revisa la cadena como si fuera un valor booleano.
static String toString (boolean b)
Regresa una cadena que representa al valor booleano es-
pecificado.
Class Byte
Es una envoltura para el tipo primitivo byte. Contiene tam-
bién algunos campos (atributos) interesantes que vamos a lis-
tar.
Atributos:
static byte MAX VALUE
Una constante que almacena el máximo valor que puede
tomar un byte, 27  1.
static byte MIN VALUE
Una constante que almacena el valor mı́nimo que puede
tomar un byte, 27 .
static int SIZE
El número de bits que se usan para representar un valor
tipo byte en notación de complemento a 2.
Métodos:
static byte parseByte( String s)
Revisa a la cadena en el parámetro para obtener un valor
decimal con signo para un byte.
static byte parseByte( String s , int radix )
Revisa a la cadena en el parámetro para obtener un valor
decimal con signo para un byte, en la base indicada por
radix .
(Continúa en la siguiente página)
4.2 Manejo de cadenas en Java 132

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (2/9)

Continúa de la página anterior

Class Character
Envuelve al tipo primitivo char.
Atributos:
static char MAX VALUE
El valor constante de este campo es el máximo valor que
se puede almacenar en una variable de tipo char, que es
el representado como "zuFFFF".
static char MIN VALUE
El valor constante de este campo es el máximo valor que
se puede almacenar en una variable de tipo char, que es
el representado como "zu0000".
static int MAX RADIX
La máxima base disponible para convertir n umeros de y
hacia cadenas.
static int MIN RADIX
La mı́nima base disponible para convertir números de y
hacia cadenas.
static int SIZE
El número de bits que se usan para representar a un de
tipo char en formato binario sin signo.
Métodos:
static int digit (char ch, int radix )
Regresa el valor numérico del carácter ch en la base espe-
cificada por radix .
static char forDigit (int digit , int radix )
Da la representación como carácter de un dı́gito especı́fico
( digit ) en la base ( radix ) especificada.
static int getNumericValue(char ch)
Regresa el valor como entero que el carácter char tiene
como código binario.
static boolean isDigit (char ch)
Determina si el carácter especificado es un dı́gito.
static boolean isLetter (char ch)
Determina si el carácter especificado es una letra
( [azAZ]).
static boolean isLetterOrDigit (char ch)
Determina si el carácter especificado es una letra
( [azAZ]) o un dı́gito ([09]).
Continúa en la siguiente página
133 Manejo de cadenas y expresiones

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (3/9)

Continúa de la página anterior

Métodos de la clase Character (continúa)

static boolean isLowerCase(char ch)


Determina si el carácter especificado es una letra
minúscula ( [az]).
static boolean isUpperCase(char ch)
Determina si el carácter especificado es una letra
mayúscula ( [az]).
static boolean isSpaceChar(char ch)
Determina si el carácter especificado es una espacio en
Unicode.
static char toLowerCase(char ch)
Convierte al carácter en el argumento a minúscula.
static char toUpperCase(char ch)
Convierte al carácter en el argumento a mayúscula.
static String toString (char ch)
Regresa una cadena que representa al carácter especifica-
do.
Class Double
Es una envoltura para números reales de doble precisión.
Atributos:
static int MAX EXPONENT
Máximo exponente que puede tener una variable de tipo
double.
static double MAX VALUE
Una constante que guarda el valor finito positivo más
grande que puede guardar una variable de tipo double,
(2  252 q  21023 ).
static int MIN EXPONENT
Mı́nimo exponente que puede tener una variable de tipo
double normalizada.
static double MIN VALUE
Una constante que guarda el valor finito positivo más
pequeño posible que puede guardar una variable de tipo
double, (21074 ).
Continúa en la siguiente página
4.2 Manejo de cadenas en Java 134

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (4/9)

Continúa de la página anterior

Atributos de la clase Double (continúa)

static double NEGATIVE INFINITY


Una constante que guarda el valor infinito negativo de
tipo double.
static double POSITIVE INFINITY
Una constante que guarda el valor infinito positivo de tipo
double.
static int SIZE
El número de bits que se usan para representar a un
double.
Métodos:
static int compare(double
$ d1, double d2)
'
'
& 0 si d1  d2
Regresa:
'

1 si d1   d2
'
% 1 si d1 ¡ d2
static double parseDouble(String s)
Regresa un de tipo double inicializado al valor represen-
tado en la cadena, convertido con el método valueOf de
esta misma clase.
static String toHexString(double d)
Regresa una cadena hexadecimal con la representación
(en bits) del número dado.
static String toString (double d)
Regresa una representación en cadena del real en el argu-
mento.
Class Float
Es una envoltura para los números reales de precisión sencilla
( float ).
Atributos:
static int MAX EXPONENT
Máximo exponente que puede tener una variable de tipo
float .
static int MIN EXPONENT
Mı́nimo exponente que puede tener una variable de tipo
float .
Continúa en la siguiente página
135 Manejo de cadenas y expresiones

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (5/9)

Continúa de la página anterior

Atributos de la clase Float (continúa)

static float MAX VALUE


Una constante que guarda el máximo valor posible para
un float , 2  223  2127 .
static float MIN VALUE
Una constante que guarda el mı́nimo valor posible para
un float , 2149 .
static float NEGATIVE INFINITY
Una constante que guarda el infinito negativo para el tipo
float .
static float POSITIVE INFINITY
Una constante que guarda el infinito positivo para el tipo
float .
static int SIZE
El número de bits que se usan para representar a un float .
Métodos:
static int compare(float $
f1 , float f2)
'
'
& 0 si f1  f2
Regresa:
'
1 si f1   f2
'
% 1 si f1 ¡ f2
static float parseFloat ( String s)
Regresa un de tipo float inicializado al valor representado
en la cadena, convertido con el método valueOf de esta
misma clase.
static String toHexString( float f )
Regresa una cadena hexadecimal con la representación
(en bits) del número dado.
static String toString ( float f )
Regresa una representación en cadena del real de tipo
float en el argumento.
Class Integer
Es una envoltura para los valores de tipo int .
Atributos:
static int MAX VALUE
Una constante que guarda el máximo valor posible para
un int , 231  1.
Continúa en la siguiente página
4.2 Manejo de cadenas en Java 136

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (6/9)

Continúa de la página anterior

Atributos de la clase Integer (continúa)

static int MIN VALUE


Una constante que guarda el mı́nimo valor posible para
un int , 231 .
static int SIZE
El número de bits que se usan para representar a un int .
Métodos:
static int bitCount(int i )
Regresa el número de bits prendidos en la representación
binaria en complemento a dos del valor int especificado.
static int parseInt ( String s)
Revisa a la cadena para convertirla a un entero con signo
static int parseInt ( String s , int radix )
Revisa a la cadena para convertirla a un entero con signo
en la base definida por radix .
static int signum(int i ) $
'
'
& 1 si i  0
Regresa el signo del entero: signum(i) 0 si i  0
'
'
% 1 si i ¡ 0
static String toBinaryString (int i )
Regresa la cadena binaria sin signo en base 2 correspon-
diente a este entero.
static String toOctalString (int i )
Regresa la cadena octal sin signo en base 2 correspon-
diente a este entero.
static String toHexString(int i )
Regresa la cadena hexadecimal sin signo en base 2 corres-
pondiente a este entero.
static String toString (int i )
Regresa la cadena que representa al entero.
static String toString (int i , int radix )
Regresa la cadena que representa al entero en base radix .
Continúa en la siguiente página
137 Manejo de cadenas y expresiones

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (8/9)

Continúa de la página anterior

Class Long
Es una envoltura para los valores del tipo long.
Atributos:
static long MAX VALUE
Una constante que guarda el máximo valor posible para
un long, 263  1.
static long MIN VALUE
Una constante que guarda el mı́nimo valor posible para
un long, 263 .
static int SIZE
El número de bits que se usan para representar a un long.
Métodos:
static long parseLong(String s)
Revisa a la cadena para convertirla a un entero con signo
static long parseLong(String s , int radix )
Revisa a la cadena para convertirla a un long con signo
en la base definida por radix .
static int signum(long i) $
'
'
& 1 si i  0
Regresa el signo del entero: signum(i) 0 si i  0
'
'
% 1 si i ¡ 0
static String toBinaryString (long i )
Regresa la cadena binaria sin signo en base 2 correspon-
diente a este long.
static String toOctalString (long i )
Regresa la cadena octal sin signo en base 2 correspon-
diente a este long.
static String toHexString(long i )
Regresa la cadena hexadecimal sin signo en base 2 corres-
pondiente a este long.
static String toString (long i )
Regresa la cadena que representa al entero.
static String toString (long i , int radix )
Regresa la cadena que representa al entero en base radix .
Continúa en la siguiente página
4.3 Implementación de una base de datos 138

Tabla 4.3 Métodos y atributos de clases envolventes de tipos primitivos (9/9)

Continúa de la página anterior

Class Short
Es una envoltura para los valores de tipo short.
Atributos:
static short MAX VALUE
Una constante que guarda el máximo valor posible para
un short, 263  1.
static short MIN VALUE
Una constante que guarda el mı́nimo valor posible para
un short, 263 .
static int SIZE
El número de bits que se usan para representar a un short.
Métodos:
static int bitCount(short i )
Regresa el número de bits prendidos en la representación
binaria en complemento a dos del valor short especificado.
static short parseShort( String s)
Revisa a la cadena para convertirla a un short con signo
static short parseShort( String s , int radix )
Revisa a la cadena para convertirla a un short con signo
en la base definida por radix .
static String toString (short i )
Regresa la cadena que representa al short.

4.3 Implementación de una base de datos

Supongamos que tenemos un conjunto de cadenas almacenadas de alguna for-


ma (nos preocuparemos de la implementación después). Por ejemplo, tengo los
nombres de los estudiantes del grupo con su carrera y quiero poder extraer datos
de allı́. Tenemos, entonces, lo que se conoce como una base de datos. Identifique-
mos las operaciones que deseamos poder hacer con esa base de datos:
139 Manejo de cadenas y expresiones

Problema: Mantener una base de datos con listas de cursos.


Descripción:
Cada objeto del curso consiste del número del grupo (una cadena), la lista de
alumnos y el número de alumnos. La lista de alumnos consiste de alumnos,
donde para cada alumno tenemos su nombre completo, su número de cuenta,
la carrera en la que están inscritos y su correo electrónico.
Las operaciones que queremos se puedan realizar son:
(a) Localizar a un estudiante, proporcionando cualquiera de sus datos, que lo
distingan de los otros estudiantes.
(b) Dado un dato particular de un estudiante, recuperar su nombre, correo,
cuenta o carrera.
(c) Agregar estudiantes.
(d) Quitar estudiantes.
(e) Poder emitir la lista de todo el grupo.
(f) Emitir la sublista de los que contienen cierto valor en alguno de sus cam-
pos.

Entonces, nuestra tarjeta de responsabilidades, en cuanto a la parte pública se


refiere, se puede ver en la figura 4.5 en la siguiente página.
Figura 4.5 Tarjeta de responsabilidades para Curso.

Clase: Curso Responsabilidades


Constructores A partir de una base de datos inicial y a partir de
P
cero.
getNombre Regresa el nombre completo de un estudiante
ú getCarrera Regresa la clave de la carrera de un estudiante
daCarrera Regresa el nombre de la carrera de un estudiante
b getCorreo Regresa el correo de un estudiante
getCuenta Regresa el número de cuenta de un estudiante
l agregaAlumno Agrega a un estudiante, proporcionando los datos co-
rrespondientes.
i eliminaAlumno Elimina al estudiante identificado para eliminar.
listaCurso Lista todos los estudiantes del curso
c losQueCazanCon Lista a los estudiantes que cazan con algún criterio
especı́fico
armaRegistro Regresa el registro “bonito” para imprimir
o
4.3 Implementación de una base de datos 140

En la tarjeta de responsabilidades usamos el prefijo get para aquellos atributos


que el sistema simplemente va a regresar, tal cual esté almacenado, y el prefija
da cuando se trata de un método de manipulación que transforma o interpreta el
campo.

De esto, podemos definir ya una interfaz de Java que se encargue de definir


estos servicios. La podemos ver en el Listado 4.3 en la página opuesta.
141 Manejo de cadenas y expresiones

Código 4.3 Interfaz para el manejo de una base de datos ServiciosCurso (1/3)

1 package C o n s u l t a s ;
2 /∗ ∗
3 ∗ I n t e r f a c e S e r v i c i o s C u r s o d e s c r i b e l o s s e r v i c i o s de un s i s t e m a de
4 ∗ b a s e s de d a t o s p a r a l o s alumnos i n s c r i t o s en un g r u p o .
5 ∗ Cre ad a : L un es Mar 8 0 9 : 1 1 : 5 5 2 0 1 0 .
6 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a ”> E l i s a V i s o </a>
7 ∗ @version 1.0
8 ∗/
9 public interface ServiciosCurso {
10
11 /∗ ∗
12 ∗ Método <code>getGrupo </code >: R e g r e s a e l número d e l g r u p o .
13 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
14 ∗/
15 S t r i n g getGrupo ( ) ;
16
17 /∗ ∗
18 ∗ Método <code>getNombre </code >: r e g r e s a e l nombre d e l
19 ∗ e s t u d i a n t e en l a p o s i c i ó n <code>c u a l </code >.
20 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >.
21 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
22 ∗/
23 S t r i n g getNombre ( i n t c u a l ) ;
24
25 /∗ ∗
26 ∗ Método <code>g e t C a r r e r a </code >: r e g r e s a l a c a r r e r a d e l
27 ∗ e s t u d i a n t e en l a p o s i c i ó n <code>c u a l </code >.
28 ∗ @param c u a l de t i p o <code>i n t </code >: P o s i c i ó n d e l r e g i s t r o .
29 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: C l a v e de l a c a r r e r a .
30 ∗/
31 String getCarrera ( int cual );
32
33 /∗ ∗
34 ∗ Método <code>d a C a r r e r a </code >: r e g r e s a e l nombre de l a c a r r e r a
35 ∗ d e l e s t u d i a n t e en l a p o s i c i ó n <code>c u a l </code >.
36 ∗ @param c u a l de t i p o <code>i n t </code >: P o s i c i ó n d e l r e g i s t r o .
37 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: Nombre de l a c a r r e r a .
38 ∗/
39 String daCarrera ( int cual ) ;
4.3 Implementación de una base de datos 142

Código 4.3 Interfaz para el manejo de una base de datos ServiciosCurso (2/3)

41 /∗ ∗
42 ∗ Método <code>g e t C o r r e o </code >: r e g r e s a e l c o r r e o d e l
43 ∗ e s t u d i a n t e en l a p o s i c i ó n <code>c u a l </code >.
44 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >.
45 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
46 ∗/
47 String getCorreo ( int cual ) ;
48
49 /∗ ∗
50 ∗ Método <code>g e t C u e n t a </code >: r e g r e s a e l número de c u e n t a d e l
51 ∗ e s t u d i a n t e en l a p o s i c i ó n <code>c u a l </code >.
52 ∗ @param c u a l v a l o r de t i p o an <code>i n t </code >.
53 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
54 ∗/
55 S t r i n g getCuenta ( int cual ) ;
56
57 /∗ ∗
58 ∗ Método <code>l o c a l i z a A l u m n o </code >: r e g r e s a l a p o s i c i ó n d e l
59 ∗ e s t u d i a n t e que c a c e con l a c a d e n a s o l i c i t a d a . .
60 ∗ @param c a d e n a v a l o r de t i p o a <code>S t r i n g </code >.
61 ∗ @ r e t u r n v a l o r t i p o an <code>i n t </code>
62 ∗/
63 i n t l o c a l i z a A l u m n o ( S t r i n g cadena ) ;
64
65 /∗ ∗
66 ∗ Método <code>agregaAlumno </code >: a g r e g a a un alumno a
67 ∗ l a b a s e de d a t o s , h a c i e n d o l a s c o n v e r s i o n e s n e c e s a r i a s .
68 ∗ @param n v a l o r de t i p o a <code>S t r i n g </code >.
69 ∗ @param ca v a l o r de t i p o a <code>i n t </code >.
70 ∗ @param c t a v a l o r de t i p o a <code>S t r i n g </code >.
71 ∗ @param co v a l o r de t i p o a <code>S t r i n g </code >.
72 ∗/
73 v o i d agregaAlumno ( S t r i n g n , i n t ca , S t r i n g c t a , S t r i n g co ) ;
74
75 /∗ ∗
76 ∗ Método <code>e l i m i n a A l u m n o </code >: e l i m i n a a l alumno en l a
77 ∗ p o s i c i ó n <code>c u a l </code> en l a b a s e de d a t o s .
78 ∗ @param c u a l v a l o r de t i p o an <code>i n t </code >.
79 ∗/
80 void eliminaAlumno ( i n t c u a l ) ;
81
82 /∗ ∗
83 ∗ Método <code>a r m a R e g i s t r o </code >: Arma e l r e g i s t r o en l a
84 ∗ p o s i c i ó n <code>c u a l </code> p a r a l i s t a r l o .
85 ∗ @param c u a l v a l o r de t i p o an <code>i n t </code >.
86 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
87 ∗/
88 String armaRegistro ( int cual ) ;
143 Manejo de cadenas y expresiones

Código 4.3 Interfaz para el manejo de una base de datos ServiciosCurso (3/3)

90 /∗ ∗
91 ∗ Método <code>d a m e L i s t a </code >: R e g r e s a l a l i s t a d e l g r u p o
92 ∗ organizada .
93 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
94 ∗/
95 S t r i n g dameLista ( ) ;
96
97 /∗ ∗
98 ∗ Método <code>dameCurso </code >: R e g r e s a t o d a e l a c t a d e l grupo ,
99 ∗ i n c l u y e n d o e l número d e l g r u p o .
100 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
101 ∗/
102 S t r i n g dameCurso ( ) ;
103
104 /∗ ∗
105 ∗ Método <code>losQueCazanCon </code >: r e g r e s a l a l i s t a de l o s
106 ∗ r e g i s t r o s que c o n t i e n e n l a s u b c a d e n a <code>q u i e n </code >.
107 ∗ @param q u i e n v a l o r de t i p o a <code>S t r i n g </code >.
108 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
109 ∗/
110 S t r i n g losQueCazanCon ( S t r i n g q u i e n ) ;
...
134 } // S e r v i c i o s C u r s o

En la interfaz que acabamos de dar, casi todos los métodos que hacen la con-
sulta trabajan a partir de saber la posición relativa del registro que queremos. Sin
embargo, una forma común de interrogar a una base de datos es proporcionándole
información parcial, como pudiera ser alguno de los apellidos, por lo que conviene
agregar un método al que le proporcionamos esta información y nos deberá decir
la posición relativa del registro que contiene esa información –listado 4.4–.
Código 4.4 Posición de un registro que contenga una subcadena ServiciosCurso

112 /∗ ∗
113 ∗ Dada una c a d e n a con e l nombre d e l alumno , r e g r e s a l a
114 ∗ p o s i c i ó n d e l r e g i s t r o que c o n t i e n e e s e nombre .
115 ∗ @param nombre E l nombre d e l alumno que buscamos .
116 ∗ @ r e t u r n Un e n t e r o que c o r r e s p o n d e a l a p o s i c i ó n
117 ∗ r e l a t i v a d e l r e g i s t r o que c o n t i e n e a l nombre .
118 ∗/
119 p u b l i c i n t d a P o s i c i o n ( S t r i n g nombre ) ;

Pudiéramos buscar una porción del registro que se repite más de una vez, y
quisiéramos que al interrogar a la base de datos ésta nos diera, uno tras otro, todos
4.3 Implementación de una base de datos 144

los registros que tienen esa subcadena. Queremos que cada vez que le pidamos no
vuelva a empezar desde el principio, porque entonces nunca pasarı́a del primero.
Le agregamos entonces un nuevo parámetro para que la búsqueda sea a partir de
un posición. El encabezado de este método se puede ver en el Listado 4.5.
Código 4.5 Posición de un registro a partir de otra posición ServiciosCurso

121 /∗ ∗
122 ∗ Dados una c a d e n a con e l nombre d e l alumno y a p a r t i r
123 ∗ de c u á l p o s i c i ó n b u s c a r , r e g r e s a l a p o s i c i ó n d e l r e g i s t r o
124 ∗ que c o n t i e n e e s e nombre , s i n e x a m i n a r a l o s que e s t á n a n t e s
125 ∗ de l a p o s i c i ó n dada .
126 ∗ @param nombre E l nombre d e l alumno que buscamos .
127 ∗ @param d e s d e A p a r t i r de donde s e va a h a c e r
128 ∗ l a b ú s q u e d a .
129 ∗ @ r e t u r n Un e n t e r o que c o r r e s p o n d e a l a p o s i c i ó n
130 ∗ r e l a t i v a d e l r e g i s t r o que c o n t i e n e a l nombre ,
131 ∗ b u s c a d o a p a r t i r de desde .
132 ∗/
133 p u b l i c i n t d a P o s i c i o n ( S t r i n g nombre , i n t d e s d e ) ;

Definida ya la interfaz procedemos a diseñar una implementación para la mis-


ma. Siguiendo la metodologı́a que tenemos para definir las clases, una vez definidas
las responsabilidades debemos decidir cuál es la información que requiere la clase
para poder brindar los servicios anunciados en la interfaz. Lo primero que necesi-
tamos es la información que corresponde a la base de datos y, de alguna manera,
la descripción de qué contiene cada registro. La manera como he decidido guardar
esta base de datos es en una cadena enorme, pero subdividida en pedazos, cada
uno del mismo tamaño. A cada uno de estos pedazos lo vamos a manejar como un
registro. Además cada registro lo vamos a dividir en campos, donde cada campo
corresponde a una unidad de información; por ejemplo, el nombre del estudiante
corresponde a un campo, ası́ como el correo electrónico a otro, el número de cuenta
a un tercero y ası́ sucesivamente. Esto nos facilita ver a la base de datos como si
fuera una tabla, donde cada renglón de la tabla corresponde a un registro y cada
columna de la tabla a un campo (o atributo). Supongamos que tenemos una lista
de alumnos como la que se muestra en la Tabla 4.4 en la página opuesta:
Como ya mencionamos, vamos a representar a la base de datos con una cadena
en la que colocaremos a todos los registros. Si no forzamos a que cada registro
ocupe el mismo número de posiciones no podrı́amos decir de manera sencilla dónde
termina un registro y empieza el siguiente. En cada uno de los registros, el número
de posiciones que ocupa, por ejemplo, el nombre también debe ser el mismo para
que podamos calcular, de manera sencilla, la posición en la cadena donde empieza
cada campo dentro de cada registro. La declaración con valores iniciales para esta
lista se verı́a, entonces, como se ve en el listado 4.6 en la página opuesta.
145 Manejo de cadenas y expresiones

Tabla 4.4 Listado del contenido de nuestra base de datos

Nombre: Carrera: Cuenta: Correo:


Aguilar Solı́s Aries Olaf Matemático 97541219-1 aguilarS. . .
Cruz Cruz Gil Noé Computación 99036358-4 cruzCruz. . .
Garcı́a Villafuerte Israel C. de la Tierra 02598658-3 garciaVi. . .
Hubard Escalera Alfredo Biologı́a 00276238-7 hubardE. . .
Tapia Vázquez Rogelio Actuarı́a 02639366-8 tapiaV. . .
... ... ... ...

Código 4.6 Ejemplo de declaración de una lista inicial para la base de datos
private String lista =
" Aguilar Solı́s Aries Olaf " + "122" + " 975412191 "
+ " aguilarS@ciencias .mx"
+ "Cruz Cruz Gil Noé " + "104" + " 990363584 "
+ " cruzCruz@ciencias .mx"
+ " Garcı́a Villafuerte Israel " + "127" + " 025986583 "
+ " garciaV@ciencias .mx "
+ " Hubard Escalera Alfredo " + "201" + " 002762387 "
+ " hubardE@ciencias .mx "
+ " Tapia Vázquez Rogelio " + "101" + " 026393668 "
+ " tapiaV@ciencias .mx " ;

Recuerden que podemos construir una cadena concatenando cadenas, usando


el operador +. Además, aparecen los caracteres blancos dentro de las cadenas con
para que podamos ver fácilmente el número de posiciones que ocupa. Dividimos la
cadena –arbitrariamente– en registros, uno por renglón, y cada registro en campos.
Noten que cada nombre ocupa 35 posiciones y cada carrera 3; el número de cuenta
ocupa 9 posiciones y el correo 20 posiciones. Cada elemento completo de la lista
ocupa 67 posiciones. Nótese también que elegimos “codificar” la carrera, pues
con tres caracteres tengo suficiente para reconocerla; para el número de cuenta
usamos 9 posiciones, pues la que estarı́a entre el octavo dı́gito y el noveno siempre
contiene un guión; por último, para el correo de usuario utilizamos 20 posiciones,
completando cuando es necesario como lo hicimos en el nombre.
La primera posición en lista es la 0 y tiene en total 5  67  335 posiciones
(de la 0 a la 334). El primer registro (i  1) empieza en la posición 0; el segundo
registro (i  2) empieza 67 posiciones después, en la posición 67. En general, el
i-ésimo registro empieza en la posición pi  1q  67. ¿Por qué i  1? Porque hay
4.3 Implementación de una base de datos 146

que “saltar” i  1 registros para llegar a donde empieza el i-ésimo.


El primer nombre empieza donde el primer registro; el segundo nombre donde
el segundo registro y ası́ sucesivamente. La carrera que corresponde al primer
registro empieza en la posición 35, una vez saltadas las primeras 35 posiciones (de
la 0 a la 34) que corresponden al nombre. La del segundo registro empieza en la
posición 102 (67+35), que corresponde a saltar las primeras 67 posiciones (de la
0 a la 66) que corresponden al primer registro, más las primeras 35 posiciones
que corresponden al nombre del segundo registro. En general, la posición de la
carrera del i-ésimo registro empieza en la posición pi  1q  67 35, donde 67
es el número de posiciones que hay que saltar por cada elemento de la tabla que
se encuentra antes que el que queremos y 35 es el desplazamiento (offset) del
campo que deseamos a partir del principio del elemento. En general, si se desea
el j-ésimo campo del i-ésimo registro, se obtiene la posición inicial del i-ésimo
registro (pi  1q  67) y a eso se le suma el total de las posiciones que ocupan los
campos desde el primero hasta el j  1.
Figura 4.6 Tarjeta de responsabilidades para Curso.

Clase: Curso Responsabilidades


Constructores A partir de una base de datos inicial y a partir
P de una lista vacı́a.
getNombre Regresa el nombre completo de un estudiante
ú daCarrera Regresa la carrera de un estudiante
b getCorreo Regresa el correo de un estudiante
getCuenta Regresa el número de cuenta de un estudiante
l agregaAlumno Agrega a un estudiante, proporcionando los
i datos necesarios.
eliminaAlumno Elimina a un estudiante después de
c identificarlo.
o listaCurso Lista todos los estudiantes del curso
losQueCazanCon Lista a los estudiantes que cazan con algún
criterio especı́fico
armaRegistro Regresa el registro “bonito” para imprimir
P lista Base de datos con los alumnos inscritos
r
i grupo Número que identifica al grupo
v número de registros En cada momento, el número de registros
a
d que contiene el grupo
o

Construyamos una clase para manejar listas de cursos. Ya tenemos, de la inter-


faz, los métodos públicos que vamos a requerir; ahora hay que decidir qué atributos
147 Manejo de cadenas y expresiones

requiere la clase para poder dar esos servicios. Si estamos hablando de un grupo
en la Facultad de Ciencias es conveniente que se guarde el número del grupo.
También es conveniente que cada base de datos me pueda responder, de manera
sencilla, el número de registros que tiene en ese momento. Estos tres atributos son
privados y en todo caso se tiene acceso a ellos a través de métodos de acceso. La
tarjeta de responsabilidades, incluyendo a estos atributos privados se encuentra
en la figura 4.6 en la página opuesta.
Como parte de la información que requiere la clase es conveniente declarar los
tamaños del registro y de los campos como constantes para poder dar expresiones
aritméticas en términos de estas constantes. De esa manera si decidimos cambiar el
tamaño de alguno de los campos únicamente tenemos que localizar la declaración
de la constante para hacerlo. El inicio de la codificación la podemos ver en el
listado 4.7.
Código 4.7 Clase que maneja listas de cursos Curso

100 package C o n s u l t a s ;
200 /∗ ∗
300 ∗ C l a s e C u r s o i m p l e m e n t a l a b a s e de d a t o s de e s t u d i a n t e s i n s c r i t o s
400 ∗ en un c u r s o . T i u e d n e l a s opc i o n e s n o r m a l e s de una b a s e de d a t o s
500 ∗ y s e r á u t i l i z a d a d e s d e un menú .
600 ∗ C r e a t e d : Lun Mar 8 0 9 : 2 5 : 3 7 2 0 1 0 .
700 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ t u r i n g ”> E ú l i s a V i s o </a>
800 ∗ @ v e r s i o n 3 . 0
900 ∗/
1000 p u b l i c c l a s s C u r s o implements S e r v i c i o s C u r s o {
1100 p r i v a t e S t r i n g g r u p o ; // C l a v e d e l g r u p o
1200 p r i v a t e S t r i n g l i s t a ; // I n f o r m a c i ó n de e s t u d i a n t e s
1300 p r i v a t e i n t numRegs ; /∗ Número t o t a l de r e g i s t r o s ∗/
1400 /∗ ∗
1500 ∗ TAM XXXX : Tamaño d e l campo
1600 ∗ OFF XXXX : D i s t a n c i a d e l campo XXXX a l p r i n c i p i o d e l r e g i s t r o
1700 ∗/
1800 private static f i n a l int
1900 TAM REG = 67 ,
2000 T NMBRE = 35 ,
2100 T CARR = 3,
2200 T CTA = 9,
2300 T CORR = 20 ,
2400 OFF NMBRE = 0 ,
2500 OFF CARRE = 3 5 ,
2600 OFF CTA = 38 ,
2700 OFF CORR = 4 7 ;

En esta pequeña prueba estamos utilizando únicamente una función de cadenas


(String), substring, que entrega la subcadena que empieza en el primer parámetro
4.3 Implementación de una base de datos 148

y termina en el segundo parámetro, ambos enteros. Este método lo invocamos


desde lista (que contiene los nombres y el resto de los datos). Un conjunto de
métodos relevantes de la clase String en esta etapa se encuentran en la tabla 4.2
en la página 125. Tenemos un método que nos encuentra la primera posición del
i-ésimo registro, daPos(int i), y a partir de ahı́ saltamos los campos que van antes
del que queremos. Ası́, el campo que corresponde al nombre está en la posición
0 de cada registro (OFF NMBRE = 0), mientras que para llegar al campo con
el correo del usuario hay que saltar el tamaño del nombre, más el tamaño de la
cuenta más el tamaño de la carrera (OFF CORR = 47 = 35 + 3 + 9). Similarmente
localizamos el inicio de cada uno de los otros campos.
Veamos ahora la implementación de los constructores en el diagrama de la
figura 4.7. En este diagrama vemos que lo que tenemos que hacer en cada uno de
los constructores es darle valor inicial a los datos privados de la clase.

Figura 4.7 Diagrama de Warnier-Orr para los constructores.


$ !
'
' Construye lista inicial Copia la referencia
'
' !
&
Constructor Calcula número de numRegs Ð tam. lista{Tam. registro
'
' estudiantes !
'
'
%Registra clave del grupo Completa 4 posiciones

Habı́amos comentado que queremos dos constructores, uno que trabaje a partir
de una lista inicial que dé el usuario y otro que inicie con una lista vacı́a. En los
dos casos se podrán agregar nombres conforme el usuario lo solicite. El código
para ambos constructores se puede ver en el listado 4.8.

Código 4.8 Constructores para la clase Curso Curso (1/2)

3700 /∗ ∗
3800 ∗ C o n s t r u y e una b a s e de d a t o s v a cı́ a p e r o con número de g r u p o .
3900 ∗ @param g r u p o t i p o <code>S t r i n g </code >: Número de g r u p o
4000 ∗/
4100 public Curso ( S t r i n g grupo ) {
4200 i n t tamanho = g r u p o . l e n g t h ( ) ;
4300 t h i s . g r u p o = tamanho > 4
4400 ? grupo . s u b s t r i n g ( 0 , 4)
4500 : ( ( tamanho < 4 ) && ( tamanho > 0 ) )
4600 ? "0000" . s u b s t r i n g (0 ,4  tamanho )
4700 : grupo ;
4800 l i s t a = "" ; // new S t r i n g ( ) ;
4900 numRegs = 0 ;
5000 }
149 Manejo de cadenas y expresiones

Código 4.8 Constructores para la clase Curso Curso (2/2)

5200 /∗ ∗
5300 ∗ C o n s t r u y e una b a s e de d a t o s a p a r t i r de l o s d a t o s que dé e l
5400 ∗ usuario .
5500 ∗ @param g r u p o t i p o <S t r i n g >: C l a v e d e l g r u p o .
5600 ∗ @param l i s t a t i p o <code>S t r i n g </code >: l i s t a b i e n armada .
5700 ∗/
5800 p u b l i c C u r s o ( S t r i n g grupo , S t r i n g l i s t a ) {
5900 i n t tamanho = g r u p o . l e n g t h ( ) ;
6000 t h i s . g r u p o = tamanho > 4
6100 ? grupo . s u b s t r i n g ( 0 , 4)
6200 : ( ( tamanho < 4 ) && ( tamanho > 0 ) )
6300 ? "0000" . s u b s t r i n g (0 ,4  tamanho )
6400 : grupo ;
6500 int tamLista = l i s t a . length ( ) ;
6600 numRegs = t a m L i s t a / TAM REG ;
6700 // V e r i f i c a que l o s r e g i s t r o s e s t é n c o m p l e t o s
6800 t h i s . l i s t a = t a m L i s t a % TAM REG == 0 // R e g i s t r o s c o m p l e t o s
6900 ? new S t r i n g ( l i s t a )
7000 : l i s t a . s u b s t r i n g ( 0 , t a m L i s t a  t a m L i s t a % TAM REG ) ;
7100 }

Ambos constructores “editan” el número de grupo, verificando que tenga cua-


tro dı́gitos. Si tiene más de cuatro lo recorta y si tiene menos de cuatro lo rellena
con ceros a la izquierda –lı́neas 4200 a 4700 y 5900 a 6400–. El primer constructor,
que trabaja a partir de un a lista inicial de alumnos, verifica que la lista tenga
registros completos –lı́neas 6800 a 7000–; si no es ası́, le quita los caracteres que
sobren más allá del último registro completo.
El método que da la lista completa es muy sencillo, ya que únicamente regresa
la lista. Lo podemos ver en el listado 4.9.

Código 4.9 Método que regresa toda la lista Curso

7400 /∗ ∗
7500 ∗ Metodo <code>g e t L i s t a </code> P r o p o r c i o n a e l a t r i b u t o
7600 ∗ <code>l i s t a </code >.
7700 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >: l a l i s t a .
7800 ∗/
7900 public String getLista () {
8000 return l i s t a ;
8100 }

Para los métodos que regresan un determinado campo de un registro tenemos


el algoritmo que se muestra en la figura 4.8 en la siguiente página.
4.3 Implementación de una base de datos 150

Figura 4.8 Diagrama de Warnier-Orr para regresar el contenido de un campo.


$ #
'
'
'
'
'
'
Calcula el inicio del lugar Ð pi  1q  TAM REG
'
' i-ésimo registro
'
'
'
' #
'
'
'
'
&Calcula el inicio del empza Ð lugar OFF XXX
Obtiene campo del campo en ese registro
i-ésimo registro '
' !
'
'
'
'
'
Calcula el final del campo final Ð empza TAM XXX
'
'
'
'
'
'
'
' Toma la subcadena entre
'
% esas dos posiciones

Como siempre nos vamos a estar moviendo al i-ésimo registro, vamos a elaborar
un método privado que nos dé la posición en la que empieza un campo dado en
el i-ésimo registro mediante la fórmula

posición  pi  1q  T AM REG OF F XXX.

donde OFF XXX es un entero que corresponde a la distancia del campo al inicio del
registro. Hay que tomar en cuenta acá al usuario, que generalmente va a numerar
los registros empezando desde el 1 (uno), no desde el 0 (cero). Con esto en mente
y de acuerdo a lo que es el primer ı́ndice en una cadena, que discutimos al inicio
de este tema, el método queda como se puede observar en el listado 4.10.

Código 4.10 Cálculo de la posición donde empieza el i-ésimo registro Curso

8300 /∗ ∗
8400 ∗ Da l a p o s i c i ó n en l a c a d e n a en l a que e m p i e z a e l campo dado en
8500 ∗ e l i é s i m o r e g i s t r o .
8600 ∗ @param c u a l de t i p o <code>i n t </code >: núm . de r e g i s t r o .
8700 ∗ @param o f f s e t de t i p o <code>i n t </code >: campo .
8800 ∗ @ r e t u r n t i p o <code>i n t </code >: P o s i c i ó n de l a c a d e n a en l a que
8900 ∗ empieza e l r e g i s t r o s o l i c i t a d o .
9000 ∗/
9100 p r i v a t e i n t daPos ( i n t c u a l , i n t o f f s e t ) {
9200 r e t u r n ( c u a l  1 ) ∗ TAM REG + o f f s e t ;
9300 }

Los métodos que regresan un campo siguen todos el patrón dado en la figura 4.8
y su implementación se puede ver en el listado 4.11 en la página opuesta.
151 Manejo de cadenas y expresiones

Código 4.11 Métodos que regresan el contenido de un campo Curso (1/2)

9500 /∗ ∗
9600 ∗ Método <code>getGrupo </code >: r e g r e s a e l v a l o r de
9700 ∗ <code>grupo </code >.
9800 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: e l v a l o r d e l a t r i b u t o .
9900 ∗/
10000 public S t r i n g getGrupo ( ) {
10100 return grupo ;
10200 }
10300
10400 /∗ ∗
10500 ∗ Método <code>getNumRegs</code >: r e g r e s a e l número de r e g i s t r o s
10600 ∗ en l a b a s e de d a t o s .
10700 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code >: Número de r e g i s t r o s .
10800 ∗/
10900 p u b l i c i n t getNumRegs ( ) {
11000 r e t u r n numRegs ;
11100 }
11200
11300 /∗ ∗
11400 ∗ Método <code>getNombre </code >: R e g r e s a e l nombre en e l
11500 ∗ registro solicitado .
11600 ∗ @param n t i p o <code>i n t </code >: e l número de r e g i s t r o .
11700 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: e l nombre s o l i c i t a d o .
11800 ∗/
11900 p u b l i c S t r i n g getNombre ( i n t c u a l ) {
12000 i n t p o s = daPos ( c u a l , OFF NMBRE ) ;
12100 r e t u r n ( ( c u a l < 0 ) | | ( c u a l > numRegs ) )
12200 ? "No existe el registro "
12300 : l i s t a . s u b s t r i n g ( pos , p o s + T NMBRE ) ;
12400 }
12500
12600 /∗ ∗
12700 ∗ Método <code>g e t C o r r e o </code >, e x t r a e e l campo c o r r e s p o n d i e n t e
12800 ∗ a correo del registro s o l i c i t a d o .
12900 ∗ @param n t i p o <code>i n t </code >: e l r e g i s t r o s o l i c i t a d o .
13000 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code >: e l campo s o l i c i t a d o .
13100 ∗/
13200 public String getCorreo ( int cual ) {
13300 i n t p o s = daPos ( c u a l , OFF CORR ) ;
13400 r e t u r n ( c u a l > numRegs )
13500 ? "El registro no existe "
13600 : l i s t a . s u b s t r i n g ( pos , p o s + T CORR ) . t r i m ( ) ;
13700 }
13800
13900 /∗ ∗
14000 ∗ Método <code>g e t C u e n t a </code >: R e g r e s a e l campo
14100 ∗ <code>c u e n t a </code> d e l r e g i s t r o s o l i c i t a d o .
14200 ∗ @param n t i p o <code>i n t </code >: e l r e g i s t r o s o l i c i t a d o .
14300 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: e l número de c u e n t a .
14400 ∗/
14500 public S t r i n g getCuenta ( int cual ) {
4.3 Implementación de una base de datos 152

Código 4.11 Métodos que regresan el contenido de un campo Curso (2/2)

14600 i n t p o s = daPos ( c u a l , OFF CTA ) ;


14700 r e t u r n ( c u a l > numRegs )
14800 ? "El registro no existe "
14900 : l i s t a . s u b s t r i n g ( pos , p o s + T CTA ) . t r i m ( ) ;
15000 }
15100
15200 /∗ ∗
15300 ∗ Mé t o d o <code>g e t C a r r e r a </code >: r e g r e s a l a c a d e n a que
15400 ∗ r e p r e s e n t a a l número de c a r r e r a .
15500
15600 ∗ @param c u a l de t i p o <code>i n t </code >: número de r e g i s t r o .
15700 ∗ @ r e t u r n de t i p o <code>S t r i n g </code >: c l a v e en c a d e n a .
15800 ∗/
15900 public String getCarrera ( int cual ) {
16000 i n t p o s = daPos ( c u a l , OFF CARRE ) ;
16100 r e t u r n ( c u a l > numRegs )
16200 ? "El registro no existe "
16300 : l i s t a . s u b s t r i n g ( pos , p o s + T CARR ) . t r i m ( ) ;
16400 }

Para regresar el nombre de la carrera hay que hacer un poco más de trabajo
pues se encuentra codificada con una cadena de tres dı́gitos, que corresponde a la
clave oficial de la carrera correspondiente. Agregamos las claves de las carreras en
la lı́nea 2800 de la clase Curso:
2800 p r i v a t e s t a t i c f i n a l S t r i n g n C a r r e r a s = " 000101104106122127201217000 " ;
En la figura 4.9 se encuentra el algoritmo en un diagrama de Warnier.
Figura 4.9 Algoritmo para obtener el nombre de la carrera
$ $
'
' '
' Obtener el registro deseado
'
' '
' !
'
' '
'
'
' &Existe Obtener clave de la carrera
'
'
'
'
'
Inicio
'
'
` #
'
' ' Avisar registro inválido
'
' '
'
'
' '
% Existe
'
' Salir
Obtener & $ $
nombre de la '
' '
' Buscar
' '
' ' ! su posición en claves
'
carrera ' '
'
'
'
'
&
& Está lugar posición 3 Ð {
'
'
'
'
'
Proceso
'
Obtener posición
de clave ''
' !
`
' ' '
'
'
'
'
'
'
'
'
'
% Está lugar 1 Ð
' %
'
' !
Obtener nombre de carrera en posición obtenida
'
'
%Final H
153 Manejo de cadenas y expresiones

Código 4.12 Método que regresan el nombre de la carrera Curso

16600 /∗ ∗
16700 ∗ Método <code>d a C a r r e r a </code >: Usa l a c l a v e de c a r r e r a
16800 ∗ r e g i s t r a d a en e l r e g i s t r o s o l i c i t a d o p a r a d e v o l v e r e l
16900 ∗ nombre de l a c a r r e r a .
17000 ∗ @param n t i p o <code>i n t </code >: número de r e g i s t r o .
17100 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: Nombre de l a c a r r e r a .
17200 ∗/
17300 public String daCarrera ( int cual ) {
17400 f i n a l i n t TAMCAD = 2 6 ;
17500 i n t p o s = daPos ( c u a l , OFF CARRE ) ;
17600 S t r i n g s c a r r = l i s t a . s u b s t r i n g ( pos , p o s + T CARR ) . t r i m ( ) . t o L o w e r C a s e ( ) ;
17700 int cualCarr = nCarreras . indexOf ( s c a r r ) ;
17800 i n t posCa = c u a l C a r r < 0 ? 0
17900 : c u a l C a r r / T CARR ;
18000 S t r i n g cadCarr =
18100 ( "No identificada " // 000
18200 + " Actuarı́a " // 101
18300 + " Ciencias de la Computación " // 104
18400 + " Fı́sica " // 106
18500 + " Matemáticas " // 122
18600 + " Ciencias de la Tierra " // 127
18700 + " Biologı́a " // 201
18800 + " Manejo Sust de Zonas Cost " ) // 217
18900 . s u b s t r i n g ( posCa ∗ TAMCAD, ( posCa + 1 ) ∗ TAMCAD)
19000 . trim ( ) ;
19100 return cadCarr ;
19200 }

Los métodos que acabamos de programar suponen que se sabe el número de


registro que se está buscando, por lo que tenemos que dar un método que dada
una subcadena regrese el número del registro que se está buscando. Como existe la
posibilidad de que no se encuentre la subcadena, se debe verificar esa posibilidad.
El algoritmo para este método se muestra en la figura 4.10.
Figura 4.10 Calcular el número de registro al que pertenece una subcadena.
$
'
'
'
'
lugar
#
Ð
la posición de la subcadena4
'
'
'
'
' Se encontró
inicio Ð
la posición del inicio del registro
'
Determinar &' lugar Ð
Calcular el número de registro
número À
de registro '
'
' "
'
'
'
'
' Se encontró lugar 1 Ð
'
'
'
%
Regresar lugar

4
Este método regresa la posición si la encuentra, o 1 si no.
4.3 Implementación de una base de datos 154

Para encontrar la posición de la subcadena simplemente usamos métodos de


la clase String. Para calcular el inicio del registro procedemos a restarle lo que
sobre del múltiplo menor más cercano del tamaño del registro; finalmente vemos
cuántos registros caben en ese número –ver diagrama de la figura 4.11–. Si la
primera presencia del nombre “López” aparece a partir de la posición 421 en la
cadena, el primer registro que contiene ese apellido empieza en la posición 414
y corresponde al registro número 10 –caben 9 registros completos antes de la
presencia de “López”–. La programación se encuentra en el listado 4.13 de la
siguiente página.
Figura 4.11 Cálculo del primer registro que contiene a una subcadena
Buscamos: “López” lugar = 609
residuo de 609  67  6, 609  6  603 Registro empieza acá
$ Reg. 0 $ Reg. 10 609  67 1  10
A l b a  G a r c ı́ a L ó p e z M a r ı́ a 
615
3 614
2 613
1
612
0
611
610
609
608
607
606
605
604
601
603

Código 4.13 Método que da el primer registro con subcadena Curso

19400 /∗ ∗
19500 ∗ Método <code>d a P o s i c i o n </code >: da e l o r d i n a l que
19600 ∗ c o r r e s p o n d e a l p r i m e r r e g i s t r o que c o n t i e n e a l a s u b c a d e n a .
19700 ∗ @param nombre t i p o <code>S t r i n g </code >: s u b c a d e n a a
19800 ∗ buscar .
19900 ∗ @ r e t u r n e l o r d i n a l d e l r e g i s t r o , o 1 s i no hay .
20000 ∗/
20100 p u b l i c i n t d a P o s i c i o n ( S t r i n g nombre ) {
20200 i n t l u g a r = l i s t a . toLowerCase ( )
20300 . i n d e x O f ( nombre . t o L o w e r C a s e ( ) ) ;
20400 i n t s o b r a n = ( l u g a r >= 0 ) ? ( l u g a r % TAM REG) : 0 ;
20500 r e t u r n ( l u g a r >= 0 ) ? ( ( l u g a r  s o b r a n ) / TAM REG) +1
20600 : lugar ;
20700 }
155 Manejo de cadenas y expresiones

Supongamos ahora que no queremos al primero que contenga a la subcadena,


sino uno que esté después de cierta posición. El algoritmo es prácticamente el
mismo, excepto que usamos otra firma de la función indexOf, la que toma en cuenta
una posición inicial a partir de dónde buscar. Le damos al método daPosicion otra
firma que tome en cuenta este parámetro adicional. La programación se encuentra
en el listado 4.14.

Código 4.14 Método que da el siguiente registro con subcadena Curso

20800 /∗ ∗
20900 ∗ Método <code>d a P o s i c i o n </code >: Da e l o r d i n a l que c o r r e s 
21000 ∗ ponde a l r e g i s t r o que c o n t i e n e a l a s u b c a d e n a , a p a r t i r
21100 ∗ de l a p o s i c i ó n dada .
21200 ∗ @param nombre t i p o <code>S t r i n g </code >: s u b c a d e n a a b u s c a r .
21300 ∗ @param d e s d e t i p o <code>i n t </code >: p o s i c i ó n a p a r t i r de
21400 ∗ la cual buscar .
21500 ∗ @ r e t u r n t i p o <code>i n t </code >: e l o r d i n a l d e l r e g i s t r o ,
21600 ∗ o 1 s i no hay
21700 ∗/
21800 p u b l i c i n t d a P o s i c i o n ( S t r i n g nombre , i n t d e s d e ) {
21900 i n t nvoReg = ( d e s d e  1 ) ∗ TAM REG ;
22000 i n t l u g a r = l i s t a . toLowerCase ( )
22100 . i n d e x O f ( nombre . t o L o w e r C a s e ( ) , nvoReg ) ;
22200 i n t s o b r a n = l u g a r % TAM REG ;
22300 r e t u r n ( l u g a r >= 0 ) ? ( ( l u g a r  s o b r a n ) / TAM REG) +1
22400 : lugar ;
22500 }

Una vez que podemos movernos en la cadena calculando el número de registro


que contiene a una cierta subcadena, podemos pasar al método que localiza a un
alumno cuando se le da una subcadena que debe estar en el registro correspon-
diente. Veamos el diagrama de Warnier correspondiente en la figura 4.12.

Figura 4.12 Algoritmo para localizar un registro


$ !
'
'
'
'
Inicio
$
lugar
#
Ð posición de la subcadena en lista
'
'
'
'
'
'
'
'
'
' Está
número Ð
' '
Localiza
& & p
lugar  plugar %TAM REGqq{TAM REG  1
Calcula registro
estudiante '
' '
'
' '
`!
'
' '
'
'
'
'
'
%Está
!
número Ð 1
'
'
% Final Regresa número
4.3 Implementación de una base de datos 156

Nuevamente tenemos dos puntos de partida para localizar una subcadena:


el principio del registro o desde una posición particular en la subcadena. En el
listado 4.15 se muestra el código para ambos métodos.

Código 4.15 Implementación de los métodos que localizan un alumno Curso

22400 /∗ ∗
22500 ∗ Método <code>l o c a l i z a A l u m n o </code >: r e g r e s a l a p o s i c i ó n d e l
22600 ∗ e s t u d i a n t e que c a c e con l a c a d e n a s o l i c i t a d a . .
22700 ∗ @param c a d e n a t i p o <code>S t r i n g </code >.
22800 ∗ @ r e t u r n v a l o r t i p o <code>i n t </code>
22900 ∗/
23000 public i n t l o c a l i z a A l u m n o ( S t r i n g subcad ) {
23100 i n t pos = d a P o s i c i o n ( subcad ) ;
23200 r e t u r n p o s < 0? 1 : p o s ;
23300 }
23400
23500 /∗ ∗
23600 ∗ Método <code>l o c a l i z a A l u m n o </code >: r e g r e s a l a p o s i c i ó n d e l
23700 ∗ e s t u d i a n t e que c a c e con l a c a d e n a s o l i c i t a d a , b u s c a n d o a p a r t i r
23800 ∗ d e l r e g i s t r o dado .
23900 ∗ @param c a d e n a t i p o <code>S t r i n g </code >: c a d e n a b u s c a d a .
24000 ∗ @param d e s d e t i p o <code>i n t </code >: a p a r t i r de .
24100 ∗ @ r e t u r n t i p o <code>i n t </code >: Número de r e g i s t r o . y
24200 ∗/
24300 p u b l i c i n t l o c a l i z a A l u m n o ( S t r i n g subcad , i n t d e s d e ) {
24400 i n t p o s = d a P o s i c i o n ( subcad , d e s d e ) ;
24500 r e t u r n p o s < 0? 1 : p o s ;
24600 }

El único método que nos falta de los que trabajan con un registro particular
es el que arma un registro para mostrarlo. El algoritmo es sumamente sencillo y
lo mostramos en la figura 4.13. Lo único relevante es preguntar si el registro que
nos piden existe o no. Para preguntar si un registro existe basta ver si su número
es válido: debe estar entre 1 y el número total de registros.

Figura 4.13 Edición del i-ésimo registro, si es que existe.


$ !
'
'Existe el registro i Arma el registro i
&
Regresar el i-ésimo À
registro ' !
'
%Existe el registro i
Di que no existe
157 Manejo de cadenas y expresiones

La programación correspondiente se encuentra en el listado 4.16. El carácter


“zt” que aparece entre cada dos elementos del listado es un tabulador, que lo que
hace es alinear bien los campos para listarlos bonito.

Código 4.16 Edición de un registro individual Curso

24800 /∗ ∗
24900 ∗ Método <code>a r m a R e g i s t r o </code >: arma e l r e g i s t r o
25000 ∗ que s e e n c u e n t r a en l a p o s i c i ó n i p a r a m o s t r a r l o .
25100 ∗ @param c u a l t i p o <code>i n t </code >: p o s i c i ó n
25200 ∗ del registro solicitado .
25300 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >: e l d e s p l i e g u e
25400 ∗ del registro .
25500 ∗/
25600 public String armaRegistro ( int cual ) {
25700 r e t u r n ( ( c u a l > 0 && c u a l <= numRegs )
25800 ? ( ( getNombre ( c u a l ) + b l a n c o s ) . s u b s t r i n g ( 0 ,T NMBRE)
25900 + "\t" +( d a C a r r e r a ( c u a l ) + b l a n c o s ) . s u b s t r i n g ( 0 , 3 0 )
26000 + "\t" + ( g e t C u e n t a ( c u a l ) + b l a n c o s ) . s u b s t r i n g ( 0 , T CTA )
26100 + "\t" + ( g e t C o r r e o ( c u a l ) + b l a n c o s ) . s u b s t r i n g ( 0 , T CORR ) )
26200 : "No se encontró al nombre buscado " ) ;
26300 }

Vale la pena mencionar que estamos “rellenando” los campos con blancos a
la derecha –por ejemplo, (daCarrera(cual) + blancos)– porque nuestros métodos nos
van a regresar los campos “podados”. Una vez que le pegamos un número fijo de
blancos lo recortamos a cierto tamaño – . substring (0,30) – también para mejorar la
apariencia de nuestras tablas.
De los procesos más comunes a hacer con una lista de un curso es listar toda
la lista completa. Sabemos cuántos registros tenemos, todo lo que tenemos que
hacer es recorrer la lista e ir mostrando uno por uno. A esto le llamamos iterar
sobre la lista. El algoritmo podrı́a ser el que se ve en la figura 4.14.

Figura 4.14 Algoritmos para listar el curso.


$ #
'
'
' sLista x”Grupo:zt + grupo
' Principio
'
'
'
'
'
i Ð 1
'
' $
& '
&arma el registro i
lista curso
'
' muestra i-ésimo registro avanza al siguiente
'
'
'
'
(mientras i  
numRegs) '%
'
'
'
' !
'
%Final Entrega la lista armada
4.3 Implementación de una base de datos 158

En Java tenemos varios enunciados compuestos que iteran. El más general de


ellos es de la forma
$
'
'
'
x
enunciado simple y
'
Ejecuta enunciado compuesto
&
xenunciado simpley
(mientras se cumpla xcondicióny) '
'. . .
'
'
%
xenunciado simpley
y su sintaxis es como se muestra en la figura 4.15.

Figura 4.15 Enunciado compuesto while.


Sintaxis:
xenunciado compuesto whiley::=
while ( xexpresión booleanay ) {
xenunciado simple o compuestoy
...
xenunciado simple o compuestoy
}
Semántica:
Lo primero que hace el programa es evaluar la xexpresión booleanay. Si ésta
se evalúa a verdadero, entonces se ejecutan los enunciados que están entre
las llaves, y regresa a evaluar la xexpresión booleanay. Si se evalúa a falso,
brinca todo el bloque y sigue con el enunciado que sigue al while.

En cualquier iteración que utilicemos, y en particular en la que acabamos de


presentar, hay varios aspectos que hay que tener presentes:
i. ¿Cuál es el estado de las variables involucradas cuando se llega por primera
vez a evaluar la condición de la iteración?
ii. ¿Cuál es el mı́nimo número de veces que se va a ejecutar el cuerpo de la
iteración?
iii. ¿Qué es lo que se hace dentro del cuerpo de la iteración que obliga a la
iteración a terminar?
En el caso de la iteración while, se debe llevar a cabo un proceso de iniciali-
zación, que consiste en, de ser necesario declarar y, asignar valores iniciales que
garanticen y dejen claro el estado al llegar a la cabeza de la iteración. Esta iteración
puede no ejecutarse, ya que la condición puede no cumplirse desde la primera vez
que se intenta iterar; por último, en el cuerpo de la iteración se debe cambiar el
estado de una o más de las variables involucradas en la condición, para que haya
posibilidad de salir de la iteración.
159 Manejo de cadenas y expresiones

Un buen ejemplo del uso de esta iteración es el encontrar el lı́mite de una


sucesión dado un margen de error. Lo que haremos será calcular sucesivamente
términos, hasta que la diferencia entre el último término calculado y el actual sea
menor que una cierta épsilon. El algoritmo para ello se encuentra en la figura 4.16
y la codificación en el listado 4.17.
1
Figura 4.16 Encontrar el lı́mite de 2n
, dado ε
$ $
'
' '
' ε = .001
' &
'
'
' double fAnt Ð 1.0 pf0 q
double fActl Ð 1.0{2.0 pf1 q
'
' Inicializar
'
' '
'
%
nÐ1
'
'
'
'
1 &
$
Calcular lı́mite de
2n '
' Calcular siguiente término 
&f Ant Ð fActl
'
'
'
'
' mientras | 
fant factl |¥
ε %f Actl Ð fAnt{2
'
' i++ (Vamos contando)
'
'
'
' !
'
%Final
Reporta fActl e i

Código 4.17 Cálculo del lı́mite de una sucesión


100 package C o n s u l t a s ;
200 import j a v a . u t i l . S c a n n e r ;
300 class Limites {
400 p u b l i c v o i d l i m i t e ( double e p s i l o n ) {
500 double f A c t l = 1 . 0 / 2 . 0 ;
600 double f A n t = 1 ;
700 int i = 2;
800 w h i l e ( Math . a b s ( f A n t  f A c t l ) >= e p s i l o n ) {
900 System . o u t . p r i n t l n ( "fAnt= " + f A n t + "\ nfActl = "+f A c t l ) ;
1000 fAnt = f A c t l ;
1100 fActl = fActl / 2;
1200 i ++;
1300 }
1400 System . o u t . p r i n t l n ( "f(n)= " + f A c t l + " con n= " + i ) ;
1500 }
1600 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
1700 S c a n n e r c o n s = new S c a n n e r ( System . i n ) ;
1800 double e p s i l o n ;
1900 L i m i t e s l i m = new L i m i t e s ( ) ;
2000 System . o u t . p r i n t ( "Dame el valor para epsilon : " ) ;
2100 e p s i l o n = cons . nextDouble ( ) ;
2200 lim . l i m i t e ( epsilon ) ;
2300 }
2400 }
4.3 Implementación de una base de datos 160

Como se puede ver, esta iteración es ideal cuando no tenemos claro el número
de veces que vamos a repetir el proceso y deseamos tener la posibilidad de no
ejecutar el cuerpo ni siquiera una vez. Por ejemplo, si el valor de ε que nos pasaran
como parámetro fuera mayor que 1{2, la iteración no se llevarı́a a cabo ni una
vez.
Hay ocasiones en que deseamos que un cierto enunciado se ejecute al menos
una vez. Supongamos para ilustrar que vamos a sumar números que nos propor-
cionen desde la consola hasta que nos den un 1. El algoritmo se puede ver en la
figura 4.17.

Figura 4.17 Sumar número mientras el usuario no de un


$ "
1
'
' suma Ð 0
'
número Ð 0
' Inicializar
'
'
'
'
'
&
Sumar una lista Sumar números "
de números Sumar número leı́do
'( mientras
' número  -1)
'
' Leer siguiente número
'
'
' !
'
'
%Final Entregar resultado

Veamos la descripción de este enunciado compuesto de iteración en la figu-


ra 4.18.
Figura 4.18 Enunciado compuesto do . . . while
Sintaxis:
xenunciado compuesto do. . . whiley::=
do {
xenunciado simple o compuestoy
xenunciado simple o compuestoy
...
xenunciado simple o compuestoy
} while ( xexpresión booleanay );
Semántica:
Lo primero que hace el enunciado al ejecutarse es llevar a cabo los enun-
ciados que se encuentran entre el do y el while. Es necesario aclarar que
estos enunciados no tienen que estar forzosamente entre llaves (ser un blo-
que) pero las llaves me permiten hacer declaraciones dentro del enunciado,
mientras que sin las llaves, como no tengo un bloque, no puedo tener decla-
raciones locales al bloque. Una vez ejecutado el bloque procede a evaluar la
xexpresión booleanay. Si ésta se evalúa a verdadero, la ejecución continúa
en el primer enunciado del bloque; si es falsa, sale de la iteración y sigue
adelante con el enunciado que sigue al do . . . while.
161 Manejo de cadenas y expresiones

También en esta iteración debemos tener cuidado en inicializar y declarar


variables necesarias antes de entrar a la iteración. No podemos declararlas dentro
porque entonces no las conoce en la xexpresión booleanay. Pero como primero
hace y después pregunta, podemos hacer la inicialización como parte del bloque.
Veamos cómo queda el pequeño algoritmo que se muestra en la figura 4.18 en la
página opuesta en el listado 4.18.
Código 4.18 Suma de números leı́dos
100 public i n t sumaLeidos ( Scanner cons ) {
200 i n t suma = 0 ;
300 i n t numero = 0 ;
400 do {
500 suma += numero ;
600 System . o u t . p r i n t ( "Dame un número . Termina con -1 --> " ) ;
700 numero = c o n s . n e x t I n t ( ) ;
800 } w h i l e ( numero !=  1);
900 r e t u r n suma ;
1000 }

Es claro que lo que se puede hacer con un tipo de iteración se puede hacer
con la otra. Si queremos que el bloque se ejecute al menos una vez usando un
while, lo que hacemos es colocar el bloque inmediatamente antes de entrar a la
iteración, como se puede observar en el listado 4.19. Esto nos va a repetir el código,
pero el resultado de la ejecución va a ser exactamente el mismo. Por otro lado, si
queremos usar un do. . . while pero queremos tener la posibilidad de no ejecutar ni
una vez, al principio del bloque ponemos una condicional que pruebe la condición,
y como cuerpo de la condicional colocamos el bloque original. De esta manera si la
condición no se cumple al principio el bloque no se ejecuta. Esto quiere decir que si
un lenguaje de programación únicamente cuenta con una de estas dos iteraciones,
sigue teniendo todo lo necesario para elaborar métodos pensados para la otra
iteración.
Código 4.19 Suma de números leı́dos con while
100 p u b l i c i n t sumaLeidosW ( S c a n n e r c o n s ) {
200 i n t suma = 0 ;
300 i n t numero = 0 ;
400 System . o u t . p r i n t ( "Dame un número . Termina con -1 --> " ) ;
500 numero = c o n s . n e x t I n t ( ) ;
600 w h i l e ( numero != 1) {
700 suma += numero ; // E l ú l t i m o que s e l e y ó
800 System . o u t . p r i n t ( "Dame un número . Termina con -1 --> " ) ;
900 numero = c o n s . n e x t I n t ( ) ;
1000 } // w h i l e
1100 r e t u r n suma ;
1200 } // sumaLeidosW
4.3 Implementación de una base de datos 162

Tenemos una tercera iteración conocida como for, que resulta ser el cañón
de las iteraciones, en el sentido de que en un solo enunciado inicializa, evalúa
una expresión booleana para saber si entra a ejecutar el enunciado compuesto e
incrementa al final de la ejecución del enunciado compuesto. Lo veremos cuando
sea propicio su uso. Por lo pronto volveremos a nuestro problema de manejar una
pequeña base de datos.
La lista del curso debemos mostrarla en algún medio. Para nuestro dispositivo
de salida usaremos System.out, mientras que para la entrada de datos utilizaremos
java.util.Scanner, a quien, como ya mencionamos antes, tendremos que importar.
Podemos regresar ahora al problema de listar todo el curso en una pantalla
proporcionada por el usuario, usando la clase Scanner y el enunciado compuesto
while. El algoritmo lo mostramos en la figura 4.19 y la programación se encuentra
en el listado 4.20 en la página opuesta.

Figura 4.19 Algoritmo para listar el grupo


! !
sListado Ð ”Grupo: ”
$
'
'
'
'
Principio Inicia cadenas grupo ”z n ”
'
' $ "
'
' ' Colocarse al principio
'
' '
' Principio
'
' '
' de la “serie”
'
' '
' #
& &
Listar todo Procesa estudiante Procesa registro
el grupo 'Lista estudiantes ' (mientras haya)
'
' '
' Avanza al siguiente
'
' '
' !
'
' '
'
'
'
'
'
%final H
'
' !
'
%final
Entrega lista

Código 4.20 Método que lista todo el curso Curso

26500 /∗ ∗
26600 ∗ Método <code>d a m e L i s t a </code >: R e g r e s a l a l i s t a d e l g r u p o
26700 ∗ organizada .
26800 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
26900 ∗/
27000 public S t r i n g dameLista () {
27100 int cual = 1;
27200 S t r i n g s L i s t a = "" ;
27300 w h i l e ( c u a l <= numRegs ) {
27400 s L i s t a = s L i s t a + "\n" + c u a l + "\t" + a r m a R e g i s t r o ( c u a l ) ;
27500 c u a l ++;
27600 }
27700 return s L i s t a ;
27800 }
163 Manejo de cadenas y expresiones

Código 4.20 Método que lista todo el curso Curso

28000 /∗ ∗
28100 ∗ Método <code>dameCurso </code >: R e g r e s a t o d a e l a c t a d e l grupo ,
28200 ∗ i n c l u y e n d o e l número d e l g r u p o .
28300 ∗ @ r e t u r n v a l o r t i p o a <code>S t r i n g </code>
28400 ∗/
28500 p u b l i c S t r i n g dameCurso ( ) {
28600 S t r i n g s C u r s o = " GRupo :\t" + g r u p o + "\n\n"
28700 + d a m e L i s t a ( ) + "\n" ;
28800 return sCurso ;
28900 }

El proceso de agregar un estudiante es bastante simple, ya que en las especifi-


caciones del problema no se menciona que la lista se tenga que mantener en orden.
Simplemente armamos el registro y lo agregamos al final. El único problema es
garantizar el tamaño de los campos, pues el usuario puede no tomarlo en cuenta.
El algoritmo se muestra en la figura 4.20.

Figura 4.20 Algoritmo para agregar un estudiante.


$
'
' Ajusta tamaño de nombre
'
'
'
& Ajusta tamaño de correo
Agrega registro Ajusta tamaño de carrera
'
' Ajusta tamaño de número de cuenta
'
'
%Agrega la cadena completa a la lista
'
Incrementa contador de registros

Para ajustar los tamaños de las cadenas, primero les agregamos al final un
montón de blancos, para luego truncarla en el tamaño que debe tener, como lo
hicimos al armar la tabla para la impresión. La programación se encuentra en el
listado 4.21 en la siguiente página.

Código 4.21 Método que agrega un estudiante a la lista Curso (1/2)

29100 /∗ ∗
29200 ∗ Método <code>agregaAlumno </code >: Crea un r e g i s t r o de alumno
29300 ∗ con l o s d a t o s d a d o s .
29400 ∗ @param nmbre t i p o <code>S t r i n g </code >: Nombre .
29500 ∗ @param c a r r e r a t i p o <code>i n t </code >: C l ; a v e de c a r r e r a .
29600 ∗ @param c u e n t a t i p o <code>S t r i n g </code >: e m a i l .
29700 ∗ @param c o r r e o t i p o <code>S t r i n g </code >: Número de c u e n t a .
29800 ∗/
4.3 Implementación de una base de datos 164

Código 4.21 Método que agrega un estudiante a la lista Curso (2/2)

29900 p u b l i c v o i d agregaAlumno ( S t r i n g nmbre , i n t c a r r e r a ,


30000 S t r i n g cuenta , S t r i n g c o r r e o ) {
30100 nmbre = ( nmbre + b l a n c o s ) . s u b s t r i n g ( 0 ,T NMBRE ) ;
30200 String sCarrera = String . valueOf ( c a r r e r a ) ;
30300 c o r r e o = ( c o r r e o + b l a n c o s ) . s u b s t r i n g ( 0 , T CORR ) ;
30400 c u e n t a = ( c u e n t a + b l a n c o s ) . s u b s t r i n g ( 0 , T CTA ) ;
30500 l i s t a = l i s t a + nmbre + s C a r r e r a + c u e n t a + c o r r e o ;
30600 numRegs++;
30700 }

No es tan fácil eliminar a un estudiante de la lista usando subcadenas. Si el


estudiante que queremos quitar es el primero, la lista se convierte en lo que queda
de la cadena al quitar el primero; si es el último el que deseamos eliminar, la
lista nueva consiste de la parte hasta inmediatamente antes de donde empieza el
último; pero en cambio, si al que deseamos eliminar se encuentra en una posición
intermedia, tenemos que partir la lista en tres pedazos: lo que va antes, el registro
que deseamos eliminar y lo que va después –ver figura 4.21–.

Figura 4.21 Posibles situaciones para eliminar a un registro.

loooooooooooooooooooooooooooooooooooooooooooooomoooooooooooooooooooooooooooooooooooooooooooooon
Caso 1

loooooooooooooooooomoooooooooooooooooon looooooooooooooooooooooooomooooooooooooooooooooooooon
Caso 2

loooooooooooooooooooooooooooooooooooooooooooooomoooooooooooooooooooooooooooooooooooooooooooooon
Caso 3

De la figura podemos ver que tenemos que hacer un análisis por casos; estamos
obligados a ello porque el método que vamos a usar para extraer subcadenas no
puede recibir una posición que no existe, que serı́a el caso si queremos eliminar
al primero o al último. El algoritmo se muestra en la figura 4.22 en la página
opuesta.
165 Manejo de cadenas y expresiones

Figura 4.22 Algoritmo para eliminar a un estudiante de la lista.


$ !
'
' Es el primero Se queda desde el segundo hasta el último
'
' À
'
' !
'
'
&Es el último Se queda desde el primero hasta el penúltimo
Eliminar el À
registro i ' !
'
'
'
' Está en medio Juntar del 1 al i-1 y del i+1 al final
'
'
'
%Disminuye numRegs

Conocemos de cuál de estas tres situaciones se trata (no hay otra posibilidad)
dependiendo de la posición del registro:
Es el primero si i vale 1.
Es el último si i vale NumRegs.
Está en medio si 1   i  NumRegs.
Para este tipo de enunciado es conveniente que introduzcamos el xenunciado
compuesto condicionaly cuya sintaxis y semántica se encuentra en la figura 4.23.

Figura 4.23 Enunciado compuesto condicional


Sintaxis:
xenunciado compuesto condicionaly::=
if ( xexpresión booleanay ) {
xenunciado simple o compuestoy
...
} else {
xenunciado simple o compuestoy
...
}
Semántica:
Lo primero que hace el programa es evaluar la xexpresión booleanay. Si
ésta se evalúa a verdadero, entonces se ejecutan los enunciados que están
entre las primeras llaves y si se evalúa a falso se ejecutan los enunciados
que están a continuación del else. Pudiéramos en ambos casos tener un
único enunciado, en cuyo caso se pueden eliminar las llaves correspondien-
tes. También podemos tener que no aparezca una cláusula else, en cuyo
caso si la expresión booleana se evalúa a falso, simplemente se continúa la
ejecución con el enunciado que sigue al if.

Con esto podemos ya pasar a programar el método que elimina al i-ésimo


registro de nuestra lista, en el listado 4.22 en la siguiente página.
4.3 Implementación de una base de datos 166

Código 4.22 Método que elimina al registro i Curso

30900 /∗ ∗
31000 ∗ Método <code>e l i m i n a A l u m n o </code >: E l i m i n a a l i é s i m o
31100 ∗ r e g i s t r o de l a b a s e de d a t o s .
31200 ∗ @param n t i p o <code>i n t </code >: E l número d e l r e g i s t r o
31300 ∗ a eliminar .
31400 ∗/
31500 public void eliminaAlumno ( i n t c u a l ) {
31600 i f ( c u a l <= 0 | | c u a l > numRegs ) { // número i n v á l i d o ?
31700 return ;
31800 }
31900 i n t p o s = daPos ( c u a l , 0 ) ;
32000 i f ( c u a l == 1 ) { // e s e l p r i m e r o
32100 i f ( numRegs > 1 ) { // No e s e l ú n i c o
32200 l i s t a = l i s t a . s u b s t r i n g (TAM REG ) ; // b r i n c a e l p r i m e r o
32300 } e l s e { // e s e l ú n i c o
32400 l i s t a = "" ;
32500 }
32600 } else {
32700 i f ( 1 < c u a l && c u a l < numRegs ) { // e s t á en medio
32800 l i s t a = l i s t a . s u b s t r i n g (0 , pos )
32900 + l i s t a . s u b s t r i n g ( p o s + TAM REG ) ;
33000 } else { // e s e l ú l t i m o
33100 l i s t a = l i s t a . s u b s t r i n g (0 , pos ) ;
33200 }
33300 }
33400 numRegs ; // Decrementa e l número de r e g i s t r o s
33500 }

El único método que nos falta, para terminar esta sección, es el que arma una
lista con todos los registros que contienen una subcadena. En este caso localizamos
al primer registro que contiene a la subcadena. A partir de ese momento, buscamos
la siguiente presencia de la subcadena a partir del siguiente registro y lo incluı́mos
en el listado. Terminamos cuando ya no haya presencia de la subcadena en lo que
resta de la lista. El algoritmos lo podemos ver en la figura 4.24.

Figura 4.24 Método que encuentra TODOS los que contienen a una subcadena.
$ #
'
'
'
' donde Ð Primer registro que caza.
cazaCon Ð ””
' Principio
'
' (cadena vacı́a)
&
#
Arma cadena
'
'
Arma siguiente cazaCon Ð cazaCon
'
' (mientras hayas + armaRegistro(donde)
'
'
'
% encontrado) donde Ð Siguiente registro que caza
167 Manejo de cadenas y expresiones

Lo único importante en este caso es darnos cuenta que ya tenemos dos ver-
siones que nos dan la posición de una subcadena, ası́ que la programación es
prácticamente directa. La podemos ver en el listado 4.23.

Código 4.23 Método que lista a los que cazan con . . . Curso

33700 /∗ ∗
33800 ∗ Método que c o n s t r u y e una l i s t a p a r a m o s t r a r con t o d o s l o s
33900 ∗ r e g i s t r o s que t i e n e n como s u b c a d e n a a l p a r á m e t r o .
34000 ∗ @param s u b c a d t i p o <code>S t r i n g </code >: La que s e b u s c a en
34100 ∗ cada r e g i s t r o
34200 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: Una c a d e n a que c o n t i e n e
34300 ∗ a l o s r e g i s t r o s que c a z a n .
34400 ∗/
34500 p u b l i c S t r i n g losQueCazanCon ( S t r i n g s u b c a d ) {
34600 i n t p o s = l i s t a . i n d e x O f ( s u b c a d ) ; // E l p r i m e r o que c a z a
34700 i n t c u a l ; // Número de r e g i s t r o
34800 S t r i n g l o s Q u e S i = "" ;
34900 w h i l e ( p o s != 1 && ( c u a l = d a C u a l ( p o s ) ) <= numRegs ) {
35000 l o s Q u e S i = l o s Q u e S i + a r m a R e g i s t r o ( c u a l ) + "\n" ;
35100 p o s = l i s t a . i n d e x O f ( subcad , c u a l ∗ TAM REG ) ;
35200 }
35300 return losQueSi ;
35400 }

Lo único merecedor de atención es la invocación al método daCual(int) que nos


regresa, dada una posición en la cadena que corresponde a la lista, el número de
registro correspondiente. Este método está en el listado 4.24.

Código 4.24 Método que calcula el número de registro a partir de una posición en la lista Curso

35600 /∗ ∗
35700 ∗ Método <code>daCual </code >: Dada l a p o s i c i ó n en l a c a d e n a
35800 ∗ r e g r e s a e l número de r e g i s t r o .
35900 ∗ @param p o s t i p o <code>i n t </code >: p o s i c i ó n en l a l i s t a .
36000 ∗ @ r e t u r n t i p o <code>i n t </code >: Número de r e g i s t r o .
36100 ∗/
36200 p r i v a t e i n t daCual ( i n t pos ) {
36300 r e t u r n p o s / TAM REG + 1 ;
36400 }

Podrı́amos, para probar que nuestros métodos funcionan, programar el método


main, pero eso es aburrido. Mejor hagamos una clase que maneje un menú para
esta base de datos en la siguiente sección.
4.4 Una clase para el menú 168

4.4 Una clase para el menú

Contar con una base de datos que tiene las funcionalidades usuales no es
suficiente. Debemos contar con algún medio de comunicación con la misma, como
pudiera ser un lenguaje de consulta (query language) o, simplemente, un menú que
nos dé acceso a las distintas operaciones que podemos realizar sobre la base de
datos. Construiremos un menú para tal efecto.
El funcionamiento de un menú queda esquematizado en el diagrama de la
figura 4.25.

Figura 4.25 Menú para uso de la clase Curso.


$ #
'
' Preparar acumuladores
'
' Principio
'
' Aplicar el menú al curso solicitado
'
' $
'
'
'
' '
' Muestra el menú
'
' '
'
'
' '
'Pide opción al usuario !
'
' '
'
'
' '
' opción == Termina Salir
'
' '
' À
'
' '
'
'
' '
' !
'
' '
' Pide registro
'
' '
' opción == Agrega
'
' '
' À Agrega registro
'
' '
'
'
' '
' $
'
' '
' '
'
' '
' '
' Pregunta a quién
'
' '
' '
' Buscarlo
'
' '
' &
'
' '
' opción == Quita Encontrado
'
' '
' À Quitarlo
'
' '
' '
'
& ' '
' !
Maneja Menú '
Menú & '
para %Encontrado Reporta
À No encontrado
Cursos '
'
'
(mientras desee
'
'
' el usuario) '
' $
'
' '
' '
'
' '
' '
' Pregunta a quién
'
' '
' '
'
'
' '
' &Buscarlo
'
' '
' Encontrado
'
' '
' opción == Busca À Reportarlo
'
' '
' '
'
'
' '
' '
' !
'
' '
' '
%Encontrado Reporta
'
' '
' No encontrado
'
' '
' À
'
' '
' !
'
' '
'
'
' '
' opción == Lista Lista todo el curso
'
' '
' À
'
' '
'
'
' '
' #
'
' '
'
'
' '
' opción == Todos los Pregunta subcadena
'
' '
% que cazan Arma la lista
'
'
'
' !
'
%Final Despedirse
169 Manejo de cadenas y expresiones

Para construir el código correspondiente al del diagrama de la Figura 4.25


tenemos en Java una condicional “calculada”, en la cual, como su nombre lo indica,
elige una opción entre varias dependiendo del valor de la variable selectora. La
sintaxis de este enunciado se encuentra en la Figura 4.26.
Figura 4.26 Enunciado switch.
Sintaxis:
switch( xexpresióny ) {
case xvalor1 y: xlista de enunciados simples o compuestosy;
case xvalor2 y: xlista de enunciados simples o compuestosy;
case . . . xlista de enunciados simples o compuestosy;
default: xlista de enunciados simples o compuestosy;
}
Semántica:
Los valores xvalor1 y, . . . deben ser del mismo tipo que la xexpresióny, a la
que llamamos la selectora del switch y deben ser constantes de ese tipo. El
enunciado switch elige un punto de entrada al arreglo de enunciados de la
lista. Evalúa la xexpresióny y va comparando contra los valores que aparecen
frente a la palabra case. El primero que coincide hace que a partir de ese
punto se ejecuten todos los enunciados hasta que se encuentre un enunciado
break, o el final del switch, lo que suceda antes. Se acostumbra colocar un
enunciado break al final de cada opción para que únicamente se ejecuten los
enunciados relativos a esa opción. El switch tiene una etiqueta de escape,
default, que puede o no aparecer. Si aparece y la expresión no toma ninguno
de los valores explı́citamente listados, se ejecuta el bloque correspondiente
a default. Si no hay etiqueta de escape y el valor de la expresión no caza con
ninguno de los valores en los case, entonces el programa abortará dando un
error de ejecución.

El comando break que mencionamos en la figura 4.26 es muy sencillo y lo único


que hace es sacar el hilo de ejecución del programa hacia afuera del switch. Su
sintaxis y semántica se encuentran definidas con más precisión en la figura 4.27.
Figura 4.27 Enunciado break.
Sintaxis:
xenunciado breaky ::= break;
Semántica:
Hace que el hilo de ejecución del programa no siga con el siguiente enun-
ciado, sino que salga del enunciado compuesto en el que está, en este caso
el switch.
4.4 Una clase para el menú 170

El enunciado break también se puede usar dentro de una iteración –en una
condicional– y hace que el hilo de ejecución del programa salga de la iteración
más cercana al break.
Veamos algunos ejemplos de la forma que podrı́an tomar distintos switches.
boolean e s t a = i !=  1;
switch ( e s t a ) {
case f a l s e : System . o u t . p r i n t l n ( "NO está " ) ;
break ;
case t r u e : System . o u t . p r i n t l n ( "SI está" ) ;
}

Como las únicas dos posibilidades para el selector del switch, una expresión
booleana, es falso o verdadero, no hay necesidad de poner una cláusula de escape,
ya que no podrá tomar ningún otro valor. Este ejemplo es realmente un enunciado
if disfrazado, ya que si la expresión se evalúa a verdadero (true) se ejecuta lo que
corresponde al caso etiquetado con true, mientras que si se evalúa a falso, se ejecuta
el caso etiquetado con false. Programado con el enunciado if queda de la siguiente
manera:
boolean e s t a = i !=  1;
i f ( e s t a ) System . o u t . p r i n t l n ( "SI está" ) ;
else System . o u t . p r i n t l n ( "SI está" ) ;

Otro ejemplo más apropiado, ya que tiene más de dos opciones, es el siguiente:
supongamos que tenemos una clave que nos dice el estado civil de una persona
y que queremos convertirlo a la cadena correspondiente. Las claves son: s para
soltero; c para casado; d para divorciado; v para viudo; y u para unión libre.
En una variable de tipo char tenemos una de estas opciones y deberemos
proporcionar una cadena con el texto adecuado. La programación quedarı́a algo
parecido a lo que se observa en el listado 4.25.

Código 4.25 Ejemplo de identificación del estado civil de un individuo


100 char e s t a d o = d a E s t a d o ( ) ;
200 /∗ d a E s t a d o ( ) a s i g n a v a l o r a e s t a v a r i a b l e ∗/
300 String texto ; // Para g u a r d a r e l l e t r e r o
400 switch ( estado ) { /∗ E l e g i m o s de a c u e r d o a l c o n t e n i d o
500 de estado ∗/
600 case ’s’ : t e x t o = " soltero " ; break ;
700 case ’c’ : t e x t o = " casado " ; break ;
800 case ’d’ : t e x t o = " divorciado " ; break ;
900 case ’u’ : t e x t o = " unión libre " ; break ;
1000 default : t e x t o = "no definido " ;
1100 } // En l a v a r i a b l e texto queda e l m e n s a j e que c o r r e s p o n d e
171 Manejo de cadenas y expresiones

En general, la estructura switch lo que hace es ir comparando el valor del


selector con cada uno de las constantes que se encuentran a continuación de la
palabra case. Pueden aparecer varias etiquetas case frente a un único bloque de
código, para referirse a que esa es la acción a realizarse en más de un caso:
case ’A’ : case ’B’ : case C :
r e t u r n " Primeros tres casos ’’

Regresamos ahora a la programación de nuestro problema. La parte correspon-


diente al manejo del menú se da en un método, para no atiborrar al método
principal (main) de la clase con código. La programación queda como se muestra
en el listado 4.26.
Código 4.26 Encabezado de la clase Menu y el método daMenu MenuCurso (1/4)

100 package C o n s u l t a s ;
200 import j a v a . u t i l . S c a n n e r ;
300 /∗ ∗
400 ∗ c l a s s MenuCurso e s l a c l a s e de u s o p a r a l a b a s e de d a t o s
500 ∗ d e l grupo .
600 ∗ C r e a t e d : Lun Jun 20 2 0 1 1 .
700 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a . f c i e n c i a s . unam . mx”>
800 ∗ E l i s a V i s o G u r o v i c h </a>
900 ∗ @version 3.0
1000 ∗/
1100 p u b l i c c l a s s MenuCurso {
1200 p r i v a t e s t a t i c f i n a l i n t FIN = 0 ,
1300 AGREGA = 1 ,
1400 BUSCA = 2 ,
1500 LISTA = 3 ,
1600 ELIGE = 4 ,
1700 QUITA = 5 ,
1800 MUESTRA = 6 ;
1900 p r i v a t e s t a t i c f i n a l S t r i n g elMenu =
2000 "[0] Terminar \n"
2100 + "[1] Agregar estudiante \n"
2200 + "[2] Buscar estudiante \n"
2300 + "[3] Listar todos \n"
2400 + "[4] Listar los que cazan con ...\n"
2500 + "[5] Eliminar estudiante \n"
2600 + "[6] Mostrar estudiante \n" ;
2700 private int opcion = 0;
2800 p r i v a t e S t r i n g s o p c i o n = "" ;
2900
3000 /∗ ∗
3100 ∗ Método <code>daMenu</code >: P i d e a l u s u a r i o l a o p c i ó n
3200 ∗ y la procesa .
4.4 Una clase para el menú 172

Código 4.26 Encabezado de la clase Menu y el método daMenu MenuCurso (2/4)

3300 ∗ @param c o n s t i p o <code>S c a n n e r </code >: d i s p o s i t i v o de s a l i d a .


3400 ∗ @param miCurso t i p o <code>Curso </code >: c u r s o a p r o c e s a r .
3500 ∗ @ r e t u r n t i p o <code>i n t </code >: o p c i ó n e l e g i d a .
3600 ∗/
3700 p u b l i c i n t daMenu ( S c a n n e r cons , C u r s o miCurso ) {
3800 s o p c i o n = "" ;
3900 opcion = 0;
4000 System . o u t . p r i n t l n ( elMenu ) ;
4100 System . o u t . p r i n t l n ( " Elige una opcion -->\t" ) ;
4200 sopcion = cons . nextLine ( ) ;
4300 i f ( s o p c i o n . l e n g t h ( ) == 0 ) { // t e c l e a r o n [ e n t e r ]
4400 System . o u t . p r i n t l n ( " Opcion invalida " ) ;
4500 return 0;
4600 }
4700 o p c i o n = " 0123456 " . i n d e x O f ( s o p c i o n . c h a r A t ( 0 ) ) ;
4800 S t r i n g quien , subcad ;
4900 int cual ;
5000 boolean b r e s p = f a l s e ;
5100 switch ( opcion ) {
5200 case FIN : System . o u t . p r i n t l n ( " Mucho gusto en haberle servido " ) ;
5300 r e t u r n  1;
5400 case AGREGA :
5500 S t r i n g nombre = pideNombre ( c o n s ) ;
5600 S t r i n g cuenta = pideCuenta ( cons ) ;
5700 S t r i n g c o r r e o = pideCorreo ( cons ) ;
5800 i n t c a r r e r a = p i d e C a r r e r a ( cons ) ;
5900 miCurso . agregaAlumno ( nombre , c a r r e r a , c o r r e o , c u e n t a ) ;
6000 r e t u r n AGREGA ;
6100 case BUSCA :
6200 System . o u t . p r i n t l n ( "Dame la cadena a buscar : " ) ;
6300 subcad = cons . n e x t L i n e ( ) ;
6400 i f ( s u b c a d . l e n g t h ( ) == 0 ) {
6500 System . o u t . p r i n t l n ( "Hubo un error de entrada " ) ;
6600 r e t u r n BUSCA ;
6700 }
6800 c u a l = miCurso . l o c a l i z a A l u m n o ( s u b c a d ) ;
6900 i f ( c u a l < 0) {
7000 System . o u t . p r i n t l n ( "No existe registro con esta cadena " ) ;
7100 r e t u r n BUSCA ;
7200 }
7300 do {
7400 System . o u t . p r i n t l n ( "El alumnos buscado es :\t"
7500 + miCurso . a r m a R e g i s t r o ( c u a l ) ) ;
7600 System . o u t . p r i n t l n ( " Deseas ver al siguiente ? (S/N)" ) ;
7700 S t r i n g sResp = cons . n e x t L i n e ( ) ;
173 Manejo de cadenas y expresiones

Código 4.26 Encabezado de la clase Menu y el método daMenu MenuCurso (3/4)

7800 i f ( s R e s p == n u l l | | s R e s p . l e n g t h ( ) == 0 ) s R e s p = "N" ;
7900 b r e s p = s R e s p . c h a r A t ( 0 ) == ’S’ ;
8000 i f (! bresp ) {
8100 r e t u r n BUSCA ;
8200 }
8300 i f ( c u a l > miCurso . getNumRegs ( ) ) {
8400 System . o u t . p r i n t l n ( "Se acabó la base de datos " ) ;
8500 r e t u r n BUSCA ;
8600 }
8700 c u a l = miCurso . l o c a l i z a A l u m n o ( subcad , c u a l ) ;
8800 } while ( cual > 0 ) ;
8900 System . o u t . p r i n t l n ( "No hay mas registros con esa subcadena " ) ;
9000 r e t u r n BUSCA ;
9100 case LISTA :
9200 System . o u t . p r i n t l n ( miCurso . d a m e L i s t a ( ) ) ;
9300 r e t u r n LISTA ;
9400 case ELIGE :
9500 System . o u t . p r i n t l n ( "Qué subcadena deben cazar :" ) ;
9600 subcad = cons . n e x t L i n e ( ) ;
9700 i f ( s u b c a d . l e n g t h ( ) == 0 ) {
9800 System . o u t . p r i n t l n ( "No se dio una cadena válida "
9900 + " a buscar " ) ;
10000 r e t u r n ELIGE ;
10100 }
10200 System . o u t . p r i n t l n ( miCurso . losQueCazanCon ( s u b c a d ) ) ;
10300 r e t u r n ELIGE ;
10400 case QUITA :
10500 System . o u t . p r i n t l n ( "Dame el alumno a eliminar " ) ;
10600 quien = cons . nextLine ( ) ;
10700 i f ( q u i e n . l e n g t h ( ) == 0 ) {
10800 System . o u t . p r i n t l n ( "Me diste una cadena inválida " ) ;
10900 r e t u r n QUITA ;
11000 }
11100 bresp = false ;
11200 cual = 0;
11300 do {
11400 c u a l = miCurso . l o c a l i z a A l u m n o ( q u i e n , c u a l ) ;
11500 i f ( c u a l < 0) {
11600 System . o u t . p r i n t l n ( "Ya no hay más con este campo " ) ;
11700 r e t u r n QUITA ;
11800 }
11900 System . o u t . p r i n t ( " Eliminar a *"
12000 + miCurso . getNombre ( c u a l ) . t r i m ( )
12100 + "*, (S/N) --> " ) ;
12200 S t r i n g sResp = cons . n e x t L i n e ( ) ;
12300 i f ( s R e s p == n u l l | | s R e s p . l e n g t h ( ) == 0 ) s R e s p = "N" ;
12400 b r e s p = s R e s p . c h a r A t ( 0 ) == ’S’ ;
12500 i f ( bresp ) {
12600 miCurso . e l i m i n a A l u m n o ( c u a l ) ;
12700 System . o u t . p r i n t l n ( " Alumno eliminado del curso " ) ;
4.4 Una clase para el menú 174

Código 4.26 Encabezado de la clase Menu y el método daMenu MenuCurso (4/4)

12800 r e t u r n QUITA ;
12900 }
13000 System . o u t . p r i n t l n ( " Deseas ver el siguiente ? (S/N)" ) ;
13100 S t r i n g sResp = cons . n e x t L i n e ( ) ;
13200 i f ( s R e s p == n u l l | | s R e s p . l e n g t h ( ) == 0 ) s R e s p = "N" ;
13300 b r e s p = s R e s p . c h a r A t ( 0 ) == ’S’ ;
13400 } w h i l e ( b r e s p && c u a l <= miCurso . getNumRegs ( ) && c u a l > 0 ) ;
13500 System . o u t . p r i n t l n ( "No se eliminó a nadie " ) ;
13600 r e t u r n QUITA ;
13700 case MUESTRA:
13800 System . o u t . p r i n t l n ( "Dame una subcadena a buscar " ) ;
13900 quien = cons . nextLine ( ) ;
14000 i f ( q u i e n . l e n g t h ( ) == 0 ) {
14100 System . o u t . p r i n t l n ( "Dato invalido " ) ;
14200 r e t u r n MUESTRA;
14300 }
14400 c u a l = miCurso . l o c a l i z a A l u m n o ( q u i e n ) ;
14500 i f ( c u a l != 1) {
14600 System . o u t . p r i n t l n ( miCurso . a r m a R e g i s t r o ( c u a l ) ) ;
14700 } else {
14800 System . o u t . p r i n t l n ( "No hay registro con esta subcadena " ) ;
14900 }
15000 r e t u r n MUESTRA;
15100 default :
15200 System . o u t . p r i n t l n ( " Opcion no existente " ) ;
15300 return 0;
15400 }
15500 }

Para el menú construimos una cadena que se muestra separada por renglones,
como se observa en las lı́neas 1900 a 2600, y le pedimos al usuario que elija un
número de opción, en las lı́neas 4000 a 4200. En esta última lı́nea aparece algo
nuevo, ya que estamos interaccionando con el usuario. La manera de hacerlo es
a través de la consola para escribir (System.out) y un objeto de la clase Scanner
para leer (por eso aparece un objeto de esta clase como parámetro). Por lo pronto
vamos a leer cadenas, pero antes de leer escribiremos en la pantalla una cadena
en la que se solicita el dato.
Estamos suponiendo que el usuario nos tecleó un dı́gito. Procedemos a ver
exactamente cuál de ellos fue, buscándolo en una cadena que contiene todas las
opciones (si tuviéramos más de 10 opciones tendrı́amos que asignar letras para
las siguientes y ası́ poder seguir utilizando este método). Esto lo hacemos con el
método ya conocido por nosotros, indexOf –lı́nea 4700 del listado 4.26–.
Una vez determinada la opción que solicitó el usuario, deberemos escoger entre
un conjunto de opciones, numeradas del 0 al 5. Para ello vamos a utilizar una
condicional especial, el switch, que se muestra en la figura 4.26.
En el caso de nuestro problema, cada bloque del switch termina con un return,
175 Manejo de cadenas y expresiones

ya que ése es el objetivo del proceso: avisar cuál es la última opción que se eligió.
Adicionalmente, se ejecuta lo correspondiente a cada opción. Las revisaremos una
por una.

4.4.1. Salir
En esta opción se emite un mensaje para el usuario, avisándole del final del
proceso, y se regresa un valor de -1 para que el programa principal ya no siga
mostrando el menú y termine –lı́neas 5200 y 5300 del listado 4.26–.

4.4.2. Agrega estudiante

Esta opción tiene que funcionar como una intermediaria (interfaz) entre el
método de la base de datos y el usuario, para agregar de manera correcta a un
estudiante. Por ello, deberemos primero llenar cada uno de los campos del registro
(serı́a absurdo pedirle al usuario que conociera cómo está implementada nuestra
base de datos). Para eso se procede a solicitarle al usuario cada uno de los campos
involucrados. Elegimos hacer un método distinto para cada campo, para poder
indicarle al usuario qué tipo de cadena estamos esperando. La codificación de
cada uno de estos métodos se encuentran en el listado 4.27 en la siguiente página.
Algunos de estos métodos los volveremos a usar, ya que nos proporcionan por
parte del usuario información. Cada uno de estos métodos simplemente le dice al
usuario qué es lo que espera y recibe una cadena que, idealmente, deberá ser lo
que el método espera –lı́neas 5400 a 6000 del listado 4.26–.
Código 4.27 Métodos para pedir datos del estudiante al usuario MenuCurso (1/2)

15100 /∗ ∗
15200 ∗ Método <code>pideNombre </code >, p i d e e l nombre d e l e s t u d i a n t e .
15300 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
15400 ∗ del usuario .
15500 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
15600 ∗/
15700 p r i v a t e S t r i n g pideNombre ( S c a n n e r c o n s ) {
15800 System . o u t . p r i n t l n ( "Dame el nombre :\t" ) ;
15900 S t r i n g nombre = c o n s . n e x t L i n e ( ) ;
16000 r e t u r n nombre ;
16100 }
16200
16300 /∗ ∗
16400 ∗ Método <code>p i d e C u e n t a </code >, p i d e e l numero de c u e n t a d e l
16500 ∗ estudiante .
16600 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s d e l u s u a r i o .
16700 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
16800 ∗/
4.4 Una clase para el menú 176

Código 4.27 Métodos para pedir datos del estudiante al usuario MenuCurso (2/2)

16900 private S t r i n g pideCuenta ( Scanner cons ) {


17000 System . o u t . p r i n t l n ( "Dame el numero de cuenta :\t" ) ;
17100 S t r i n g cuenta = cons . nextLine ( ) ;
17200 return cuenta ;
17300 }
17400
17500 /∗ ∗
17600 ∗ Método <code>p i d e C o r r e o </code >, p i d e e l c o r r e o d e l e s t u d i a n t e .
17700 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s d e l
17800 ∗ usuario .
17900 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
18000 ∗/
18100 private S t r i n g pideCorreo ( Scanner cons ) {
18200 System . o u t . p r i n t l n ( "Dame el correo :\t" ) ;
18300 S t r i n g c o r r e o = cons . nextLine ( ) ;
18400 return correo ;
18500 }
18600
18700 /∗ ∗
18800 ∗ Método <code>p i d e C a r r e r a </code >, p i d e l a c a r r e r a d e l e s t u d i a n t e
18900 ∗ y v e r i f i c a que s e a una c l a v e v á l i d a .
19000 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s d e l
19100 ∗ usuario .
19200 ∗ @ r e t u r n t i p o <code>i n t </code >, e l d a t o p r o p o r c i o n a d o .
19300 ∗/
19400 private i nt p i d e C a r r e r a ( Scanner cons ) {
19500 i n t c a r r e r a =  1;
19600 while ( c a r r e r a < 0) {
19700 System . o u t . p r i n t l n ( "Dame la clave de carrera : \n"
19800 + " (101) Actuarı́a \n"
19900 + " (201) Biologı́a \n"
20000 + " (104) Ciencias de la Computación \n"
20100 + " (127) Ciencias de la Tierra \n"
20200 + " (106) Fı́sica \n"
20300 + " (217) Manejo Sust de Zonas Cost\n"
20400 + " (122) Matemáticas \n--> " ) ;
20500 c a r r e r a = cons . n e x t I n t ( ) ;
20600 c o n s . n e x t L i n e ( ) ; // Para u s a r e l r e t o r n o de c a r r o
20700 i f (! esCarrera ( carrera )) {
20800 System . o u t . p r i n t l n ( " Carrera inválida "
20900 + "\ nIntenta otra vez" ) ;
21000 c a r r e r a =  1;
21100 }
21200 }
21300 return c a r r e r a ;
21400 }
177 Manejo de cadenas y expresiones

Es necesario verificar, cuando nos proporcionan la clave de una carrera, que


sea correcta. En la lı́nea 20700 invocamos a un método que hace esto. El método
se encuentra en el listado 4.28 y hace lo siguiente:
i. Convierte el entero a cadena, rellenando con ceros por la izquierda si es ne-
cesario –lı́neas 224 a 226–.
ii. Verifica que esta subcadena se encuentre en la lista de claves –lı́nea 227–.
iii. Finalmente verifica que la haya encontrado (!= 1) y que empiece en la pri-
mera posición de alguna clave de tres dı́gitos ( % 3 == 0) –lı́nea 228–.

Código 4.28 Verificación de que la clave de carrera es correcta MenuCurso

216 /∗ ∗
217 ∗ Método <code>e s C a r r e r a </code >: V e r i f i c a que l a c l a v e de l a
218 ∗ c a r r e r a s e a v á l i d a . .
219 ∗ @param c a r r e r a v a l o r de t i p o <code>i n t </code> p a r a
220 ∗ @ r e t u r n t i p o <code>b o o l e a n </code >: c o r r e c t a o i n c o r r e c t a .
221 ∗/
222 p r i v a t e boolean e s C a r r e r a ( i n t c a r r e r a ) {
223 f i n a l S t r i n g n C a r r e r a s = " 000101104106122127201217000 " ;
224 String sCarrera = String . valueOf ( c a r r e r a ) ;
225 i n t tam = s C a r r e r a . t r i m ( ) . l e n g t h ( ) ;
226 s C a r r e r a = "0000" . s u b s t r i n g (0 ,3  tam ) + s C a r r e r a ;
227 i n t pos = n C a r r e r a s . indexOf ( s C a r r e r a ) ;
228 i f ( p o s == 1 | | p o s % 3 != 0 ) r e t u r n f a l s e ;
229 return true ;
230 }

4.4.3. Quita estudiante

En esta opción también la parte interesante la hace el método de la base de


datos que ya revisamos. Debemos notar, sin embargo, que es acá donde vamos a
verificar que las operaciones sobre la base de datos se hayan realizado de manera
adecuada, preguntando siempre por el resultado de las mismas. También debemos
verificar si hay algún otro estudiante con la misma subcadena en caso de que no
se haya encontrado al que el usuario tenı́a en mente. Para ello entramos en un
ciclo del que saldremos cuando el usuario decida, o bien no haya más estudiantes
con la caracterı́stica solicitada. El código de este ciclo se puede ver en las lı́neas
11300 a 13400 del listado 4.26 en la página 173. El código de la opción completa
se encuentra en en el mismo listado, lı́neas 10400 a 13600.
4.4 Una clase para el menú 178

4.4.4. Busca estudiante

También realiza su tarea usando métodos que ya explicamos. Al igual que las
otras opciones, verifica que las operaciones sobre la base de datos se realicen de
manera adecuada. También acá debe mostrar, de ası́ desearlo el usuario, uno por
uno los registros que contienen a la subcadena. Esto se realiza de la lı́nea 7300
a 8800 del listado 4.26 en la página 172. Toda la opción se encuentra de la lı́nea
6100 a la 9000 del mismo listado.

4.4.5. Lista todo el curso

Simplemente invoca al método correspondiente de la base de datos –lı́neas 9100


a 9300 del listado 4.26–.

4.4.6. Lista los que cumplan con algún criterio

Este método verifica si algún estudiante cumplió o no con el criterio solicitado,


preguntando si la cadena resultante tiene algo o no. Noten que tiene que usar
el método equals(String) ya que de otra manera no estarı́a comparando conteni-
dos de cadenas y nos dirı́a siempre que no son iguales –lı́neas 9400 a 10300 del
listado 4.26–.

4.4.7. Valor por omisión

En este caso, regresaremos un valor que permita al usuario saber que no dio una
opción correcta y que debe volver a elegir –lı́neas 15100 a 15300 del listado 4.26–.

4.4.8. Método principal de MenuCurso

En este método es en el que tenemos que declarar nuestros objetos, tanto la


base de datos como el menú –lı́neas 24100 a 25300 del listado 4.29–. Hay que
crear un objeto de la clase MenuCurso ya que siendo main un método estático,
de la clase, no tiene acceso ni a los atributos ni a los métodos de la clase; éstos
sólo son accesibles desde algún objeto de la clase. Una vez hecho esto simplemente
entraremos en una iteración mostrando el menú y recibiendo la opción, mientras
179 Manejo de cadenas y expresiones

el usuario no decida terminar. La programación de este método se encuentra en


el listado 4.29, lı́neas 23800 a 26000.

Código 4.29 Método principal (main) de la clase MenuCurso MenuCurso

23800 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
23900 S c a n n e r c o n s = new S c a n n e r ( System . i n ) ;
24000 System . o u t . p r i n t l n ( " Bienvenido al Sistema de registro " ) ;
24100 C u r s o miCurso = new C u r s o ( "7050" , // C u r s o
24200 " Aguilar Solı́s Aries Olaf " + "122"
24300 + " 400001528 " + " aaguilar "
24400 + "Cruz Cruz Gil Noé " + "104"
24500 + " 098034011 " + " ncruz "
24600 + " Garcı́a Villafuerte Israel " + "127"
24700 + " 098159820 " + " igarcia "
24800 + " Hubard Escalera Alfredo " + "201"
24900 + " 099586197 " + " ahubard "
25000 + " Tapia Vázquez Rogelio " + "101"
25100 + " 098162075 " + " rtapia " // l i s t a
25200 );
25300 MenuCurso menu = new MenuCurso ( ) ;
25400 int resp = 0;
25500 do {
25600 r e s p = menu . daMenu ( cons , miCurso ) ;
25700 } w h i l e ( r e s p >= 0 ) ;
25800 System . o u t . p r i n t l n ( "Fue un placer servirte " ) ;
25900 cons . c l o s e ( ) ;
26000 }

Con esto damos por terminada nuestra primera aproximación a una base de
datos. En lo que sigue haremos más eficiente y flexible esta implementación, bus-
cando que se acerque más a lo que es una verdadera base de datos.

4.5 Colofón

Vale la pena mencionar un “truco” que se llevó a cabo repetidamente en la


aplicación para simplificar la programación: extendimos las cadenas más allá de
su final normal para no preocuparnos por dar un carácter a la derecha que en
realidad no existe. Esto lo hicimos para rellenar cadenas con blancos y para elegir
la carrera correspondiente.
4. Ejercicios 180

Ejercicios

4.1.- Tenemos los siguientes atributos declarados en una clase:


p r i v a t e i n t num = 0 ,
l i n = 0;
p r i v a t e S t r i n g c a d e n a = "" ;

y el siguiente método en el que queremos leer los valores para estos tres
atributos:
public void leeDatos ( Scanner cons ) {
System . o u t . p r i n t l n ( "Dame los datos a leer :\n"
+ "en el siguiente orden : (int) (int) ( String )" ) ;
num = c o n s . n e x t I n t ( ) ;
cons . nextLine ( ) ;
l i n = cons . n e x t I n t ( ) ;
cons . nextLine ( ) ;
cadena = cons . n e x t L i n e ( ) ;
cons . nextLine ( ) ;
}

Dı́ exactamente cómo tienen que teclearse los datos (o aparecer en un archivo
de texto) para que se asignen bien los valores.

4.2.- Cuáles son los enunciados que acomodan una cadena y un entero que apa-
recen como sigue:
1785
E s t a e s una c a d e n a

4.3.- Supongamos que tenemos dos cadenas:


S t r i n g cad1 = " Otra vez mas ";
S t r i n g cad2 = " OTRA VEZ MAS " ;

y queremos que al compararlas nos diga la aplicación que son iguales. ¿Cómo
es el enunciado de comparación de cadenas que lograrı́á esto?

4.4.- Tengo las claves de categorı́á y nivel de los profesores de la Facultad de


Ciencias:
AA Asociado A AB Asociado B
AC Asociado C TA Titular A
TB Titular B TC Titular C
181 Manejo de cadenas y expresiones

Escribe los enunciados necesarios (de declaración y funciones de cadenas)


para que, dada una de las claves válidas, se imprima el nombre de la cate-
gorı́a. Debes verificar que la clave es válida.

4.5.- Escribe un método que recibe como argumentos una cadena que va a replicar
y un entero –el número de veces que debe replicar la cadenas; regresa la
cadena replicada el número de veces.

4.6.- Como en el ejercicio anterior, pero con un tercer parámetro que le dice el
tamaño máximo de la cadena que regresa, para lo que deberá truncar la
última réplica.

4.7.- Tenemos el siguiente diagrama de Warnier para obtener el númnero de pre-


sencias de una subcadena en una cadena.
$ $
' '
'
'
' &Obtener Cadena
'
'
'
' Inicio Obtener subcadena
'
' '
'
'
' %Buscar primera presencia
Obtener '
&
número de #
presencias '
'
' Cuenta presencia contador++
'
' (mientras haya) Buscar siguiente presencia
'
'
'
'
'
' !
'
%Final
Reportar contador

4.8.- Dibuja el diagra de Warnier y codifica un método que recibe como entrada
una cadena cad, una subcadena subcad y un entero n. El método debe loca-
lizar la presencia n–ésima de sucad en cad y regresar la subcadena de subcad
que contiene n presencias de subcad y termina exactamente donde termina
la n–ésima presencia de subcad.

4.9.- Codifica un método que suma cuatro enteros que recibe como parámetros.
Codifica tambnién la llamada a este método, donde los argumentos con los
que se llama son los primeros cuatro que se usaron para invocar a la clase
(en main).

4.10.- Codifica un método que escriba si la suma de sus argumentos, que son
enteros, es mayor que cero, igual a cero o menor que cero.

4.11.- Escribe un método que reciba como parámetro un carácter y le asigne una
categorı́a de la siguiente manera:
4. Ejercicios 182

De l a ’a’ , ’e’ , ’i’ , ’o’ , ’u’ : v o c a l e s


’á’ , ’é’ , ’i’ , ’ó’ , ’ú’ : v o c a l e s a c e n t u a d a s
’0’ . . . ’9’ : dı́ g i t o s
el resto : resto

El método tiene que estar imp[lementado con un switch.

4.12.- Tenemos el siguiente pedazo de código escrito con un while. Conviértelo a


un do . . . while
i n t i = c o n s . n e x t I n t ( ) ; // Suponemos c o n s i n i c i a l i z a d o adecuadamente
i n t k = cons . n e x t I n t ( ) ;
int cont = 0;
i n t suma ;
w h i l e ( ++i < k ) {
c o n t ++;
suma += i ;
}

4.13.- Escribe el código necesario para que dado un entero entre 0 y 9, se escriba
con letro el enter –): cero, 1:uno, y ası́ sucesivamente–.

4.14.- Tenemos el siguiente pedazo de código escrito con if anidados que escribe
con letra cualquier número entre 0 y 29. Conviértelo a un switch.
public String convierte ( int i ) {
S t r i n g v a l o r = "" ;
// O b t e n e r dı́ g i t o de d e c e n a s
i n t d e c e n a s = i % 30 / 1 0 ; // 0 <= d e c e n a s < 3
// O b t e n e r u n i d a d e s
i n t u n i d a d = ( i % 3 0 ) % 1 0 ; // 0 <= u n i d a d <= 9
System . o u t . p r i n t l n ( "i=" + i + "\ tdecimal ="
+ d e c e n a s + "\ tunidad ="
+ unidad ) ;
i f ( d e c e n a s == 0 ) {
i f ( u n i d a d e s == 0 ) {
v a l o r = "cero" ;
}
} else {
i f ( d e c e n a s == 1 ) {
i f ( u n i d a d == 1 ) {
v a l o r = "once" ;
} else {
i f ( u n i d a d == 2 ) {
v a l o r = "doce" ;
} else {
i f ( u n i d a d == 3 ) {
183 Manejo de cadenas y expresiones

v a l o r = " trece " ;


} else {
i f ( u n i d a d == 4 ) {
v a l o r = " catorce " ;
} else {
i f ( u n i d a d == 5 ) {
v a l o r = " quince " ;
} else {
i f ( u n i d a d == 0 ) {
v a l o r = "diez" ;
} else {
v a l o r = " dieci " ;
}
}
}
}
}
}
}
else {
i f ( d e c e n a s == 2 ) {
i f ( u n i d a d == 0 ) {
v a l o r = " veinte " ;
}
else {
v a l o r = " veinti " ;
}
}
}
}
i f ( u n i d a d >= 6 ) {
i f ( u n i d a d == 6 ) {
v a l o r += "seis" ;
} else {
i f ( u n i d a d == 7 ) {
v a l o r += " siete " ;
} else {
i f ( u n i d a d == 8 ) {
v a l o r += "ocho" ;
} else {
i f ( u n i d a d == 9 ) {
v a l o r += " nueve " ;
}
}
}
}
}
i f ( d e c e n a s == 0 | | d e c e n a s == 2 ) {
4. Ejercicios 184

i f ( u n i d a d == 1 ) {
v a l o r += "uno" ;
} else {
i f ( u n i d a d == 2 ) {
v a l o r += "dos" ;
} else {
i f ( u n i d a d == 3 ) {
v a l o r += "tres" ;
} else {
i f ( u n i d a d == 4 ) {
v a l o r += " cuatro " ;
} else {
i f ( u n i d a d == 5 ) {
v a l o r += " cinco " ;
}
}
}
}
}
}
r e t u r n v a l o r + "***" ;
}
Datos estructurados
5
La implementación que dimos en el capı́tulo anterior para nuestra base de
datos es demasiado alejada de cómo abstraemos la lista del grupo. Realmente,
cuando pensamos en una lista es una cierta colección de registros, donde cada
registro tiene uno o más campos. Tratemos de acercarnos un poco más a esta
abstracción.
Lo primero que tenemos que definir es qué es lo que queremos decir con una
“estructura de datos”, lo que hacemos a continuación:
Definición 5.1 Una estructura de datos es una colección de datos, organizada de
cierta manera y que permite el acceso a sus elementos siguiendo una determinada
disciplina.
Las estructuras de datos pueden ser:

Dinámicas o estáticas, que pueden o no cambiar el número de sus elementos


durante ejecución, respectivamente.
Homogéneas o heterogéneas, donde todos sus elementos son del mismo tipo
o de varios tipos distintos, respectivamente.
Lineales o no lineales, donde se puede formar a los elementos en una sola
fila (enumerarlos) uno después del otro o no hay manera única de hacer esto,
respectivamente.
De acceso directo o secuencial, si es que se puede obtener a un elemento
en particular extrayéndolo directamente o se tiene que recorrer a toda la
estructura desde el principio para encontrar al elemento que se desea.
186

Las cadenas de caracteres son estructuras de datos lineales –cada carácter


ocupa una posición numerada del cero en adelante–, estáticas –las cadenas, una
vez construidas, no cambian ni de tamaño ni de contenido–, homogéneas –todos
sus elementos son caracteres– y de acceso directo –mediante el método charAt–.
Los objetos también son estructuras de datos lineales –podemos dar una lista de
los atributos, de donde los podemos ’formar’ uno después del otro–, estáticas –cada
objeto construido tiene un tamaño definido ya que está formado por variables de
tipo primitivo o de tipo referencia– y de acceso directo –a través del nombre del
campo–, pero son heterogéneas –cada atributo puede ser de un tipo distinto–. Los
dispositivos como el Scanner son estructuras de datos lineales, homogéneas, de
acceso secuencial y dinámicas.
El tipo de colección que usaremos en esta ocasión es una lista. La definición
de una lista es la siguiente:
Definición 5.2 Una lista ℓ es:
i. La lista vacı́a, aquella que no tiene ningún elemento –representada
formalmente como [ ] o ∅–;
o bien
ii. el primer elemento de la lista, seguido de una lista –representado
formalmente como (a,ℓ), donde ℓ es a su vez una lista–.
Por ejemplo, si una lista nada más tiene un elemento, ésta consiste del primer
elemento de una lista, seguido de una lista vacı́a: (a, [ ]) . Una lista con tres
elementos, a, b y c se representa como (a, (b, (c, [ ]) , o sea la lista cuyo primer
elemento es a seguida de la lista cuyo primer elemento es b, seguida de la lista
cuyo primero elemento es c, seguida de la lista vacı́a. Podemos usar una notación
abreviada de lo que es una lista usando corchetes y omitiendo la lista vacı́a con la
que terminan todas las listas. Ası́, (a, [ ]) se representa como [a] , mientras que
una lista con tres elementos a, b y c, en ese orden, quedarı́a como [a, b, c] ; la
lista vacı́a se sigue representando con [ ].
Las listas son estructuras de datos lineales –listamos en orden a sus elemen-
tos–, homogéneas –todos sus elementos son del mismo tipo–, dinámicas –tenemos
operaciones que agregan elementos o quitan elementos de las listas– y de acce-
so secuencial –para llegar al tercer elemento tenemos que ir desgranando la lista
usando a los primeros elementos de las sublistas que la componen–.
Toda lista es una referencia a objetos de cierto tipo que contienen determinada
información y donde al menos uno de sus campos es una referencia, para acomodar
allı́ a la lista que le sigue. En Java, si una lista es la lista vacı́a tendrá el valor de
null, que corresponde a una referencia nula. Generalmente representamos las listas
como se muestra en la figura 5.1 en la página opuesta, con “@r” representando un
campo (atributo) para guardar allı́ una referencia y el sı́mbolo [ ] representando
187 Datos estructurados

que no sigue nadie (una referencia nula).


Como la definición de la lista es recursiva, debemos siempre tener “anclado”
al primer elemento de la lista, la cabeza de la misma, como se puede ver en la
figura 5.1.

Figura 5.1 Ilustración de una lista


cabeza: al primero de la lista
r
Inf o @r Inf o @r Inf o @r Inf o @r
[]

Otros nombres que se usan en la literatura sinónimos de referencia son liga,


apuntador, cadena1 .

5.1 La clase para cada registro (estudiante)

Como en el capı́tulo anterior, queremos que cada estudiante de la lista tenga


un campo para nombre, cuenta, carrera y clave, pero en esta ocasión lo haremos
independiente cada uno. Además requerimos el campo donde se guarde la referen-
cia al siguiente de la lista. Esta referencia es una referencia a objetos de la misma
clase que los registros: es auto referida.
Como lo dicta la orientación a objetos, primero haremos una interfaz que me
describa los servicios que debe dar la clase que represente a cada uno de los
registros del estudiante, a la que llamaremos ServiciosEstudiante. Requerimos los
mismos servicios que antes, que arme el nombre, la carrera, etcétera, pero además,
como ahora se trata de campos en un objeto, también tendremos los métodos get
y set correspondientes. El código de esta interfaz, que no merecer más explicación,
se encuentra en el listado 5.1 en la siguiente página. Para la interfaz no requerimos
de dispositivos de entrada y salida, por lo que no importamos nada.

1
Del inglés, chain.
5.1 La clase para cada registro (estudiante) 188

Código 5.1 Interfaz para los servicios del registro de estudiantes ServiciosEstudiante (1/4)

1 package C o n s u l t a s L i s t a s ;
2 /∗ ∗
3 ∗ I n t e r f a z <code>S e r v i c i o s E s t u d i a n t e </code> d e s c r i b e l o s s e r v i c i o s
4 ∗ que d a r á l a c l a s e <code>E s t u d i a n t e </code >.
5 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ c i e n c i a s . f c i e n c i a s . unam . mx”>
6 ∗ E l i s a V i s o G u r o v i c h </a>
7 ∗ @version 1.0
8 ∗/
189 Datos estructurados

Código 5.1 Interfaz para los servicios del registro de estudiantes ServiciosEstudiante (2/4)

9 public interface ServiciosEstudiante {


10 /∗ ∗
11 ∗ Método <code>getNombre </code >: r e g r e s a campo <code>nombre</code >.
12 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c o n t e n i d o d e l campo .
13 ∗/
14 p u b l i c S t r i n g getNombre ( ) ;
15
16 /∗ ∗
17 ∗ Método <code>g e t C u e n t a </code >: r e g r e s a campo <code>c u e n t a </code >.
18 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c o n t e n i d o d e l campo .
19 ∗/
20 public S t r i n g getCuenta ( ) ;
21
22 /∗ ∗
23 ∗ Método <code>g e t C o r r e o </code >: r e g r e s a campo<code>c o r r e o </code >.
24 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c o n t e n i d o d e l campo .
25 ∗/
26 public String getCorreo ( ) ;
27
28 /∗ ∗
29 ∗ Método <code>g e t C a r r e r a </code >: r e g r e s a c o n t e n i d o d e l campo
30 ∗ <code>c a r r e r a </code >.
31 ∗ @ r e t u r n t i p o <code>i n t </code >: c o n t e n i d o d e l campo .
32 ∗/
33 public int getCarrera ( ) ;
34
35 /∗ ∗
36 ∗ Método <code>g e t S i g u i e n t e </code >: r e g r e s a c o n t e n i d o d e l campo
37 ∗ <code>s i g u i e n t e </code >.
38 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >: c o n t e n i d o d e l campo .
39 ∗/
40 public Estudiante getSiguiente ( ) ;
41
42 /∗ ∗
43 ∗ Método <code>setNombre </code >: a c t u a l i z a <code>nombre</code >.
44 ∗ @param n t i p o <code>S t r i n g </code> p a r a nuevo v a l o r de nombre .
45 ∗/
46 p u b l i c v o i d setNombre ( S t r i n g n ) ;
47
48 /∗ ∗
49 ∗ Método <code>s e t C u e n t a </code >: a c t u a l i z a <code>c u e n t a </code >.
50 ∗ @param c t i p o <code>S t r i n g </code> p a r a nuevo v a l o r de c u e n t a .
51 ∗/
52 public void setCuenta ( S t r i n g c ) ;
5.1 La clase para cada registro (estudiante) 190

Código 5.1 Interfaz para los servicios del registro de estudiantes ServiciosEstudiante (3/4)
54 /∗ ∗
55 ∗ Método <code>s e t C o r r e o </code >: a c t u a l i z a <code>c o r r e o </code >.
56 ∗ @param c t i p o <code>S t r i n g </code >: nuevo v a l o r
57 ∗ de <code>c o r r e o </code >.
58 ∗/
59 public void s e t C o r r e o ( S t r i n g c ) ;
60
61 /∗ ∗
62 ∗ Método <code>s e t C a r r e r a </code >: a c t u a l i z a <code>c a r r e r a </code >.
63 ∗ @param c t i p o <code>i n t </code >: p a r a e l nuevo v a l o r de
64 ∗ <code>c a r r e r a </code >.
65 ∗/
66 public void s e t C a r r e r a ( i n t c ) ;
67
68 /∗ ∗
69 ∗ Método <code>s e t S i g u i e n t e </code >: a c t u a l i z a e l campo
70 ∗ <code>s i g u i e n t e </code >.
71 ∗ @param s t i p o <code>E s t u d i a n t e </code> p a r a
72 ∗ e l nuevo v a l o r de <code>s i g u i e n t e </code >.
73 ∗/
74 public void s e t S i g u i e n t e ( E s t u d i a n t e s ) ;
75
76 /∗ ∗
77 ∗ <code>daNombre</code >: Arma c a d e n a de tamaño f i j o con e l
78 ∗ <code>nombre</code> y l a r e l l e n a con b l a n c o s a l a d e r e c h a .
79 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: l a c a d e n a r e l l e n a d a .
80 ∗/
81 p u b l i c S t r i n g daNombre ( ) ;
82
83 /∗ ∗
84 ∗ Método <code>daCuenta </code >: arma una c a d e n a de tamaño f i j o
85 ∗ con e l campo <code>c u e n t a </code> r e l l e n a n d o con c e r o s p o r
86 ∗ la izquierda .
87 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: l a c a d e n a con c e r o s .
88 ∗/
89 p u b l i c S t r i n g daCuenta ( ) ;
90
91 /∗ ∗
92 ∗ Método <code>d a C a r r e r a </code >: r e g r e s a e l nombre de l a c a r r e r a
93 ∗ con un tamaño f i j o , r e l l e n a d o de b l a n c o s a l a d e r e c h a .
94 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a r r e r a r e l l e n a d a con
95 ∗ blancos .
96 ∗/
97 public String daCarrera ( ) ;
98
99 /∗ ∗
100 ∗ Método <code>d a C o r r e o </code >: r e g r e s a una c a d e n a de tamaño f i j o
101 ∗ con e l c o r r e o d e l e s t u d i a n t e s o l i c i t a d o , r e l l e n a d a con b l a n c o s
102 ∗ por l a derecha .
103 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l c o r r e o s o l i c i t a d o .
104 ∗/
105 public S t r i n g daCorreo ( ) ;
191 Datos estructurados

Código 5.1 Interfaz para los servicios del registro de estudiantes ServiciosEstudiante (4/4)

107 /∗ ∗
108 ∗ Método <code>a r m a R e g i s t r o </code >: arma e l r e g i s t r o p a r a m o s t r a r ,
109 ∗ j u n t a n d o t o d a s l a s c a d e n a s en e l r e g i s t r o .
110 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l r e g i s t r o armado .
111 ∗/
112 public String armaRegistro ( ) ;
113 }

Los métodos que empiezan con get y set tienen el significado acostumbrado,
trabajando directamente con el estado del objeto. Podemos ver en el listado,
además, los métodos que editan los campos y que empiezan con da. Por último,
tenemos también el método que arma todo el registro.
Hemos decidido manejar un catálogo de carreras para unificar el acceso al mis-
mo. La clase correspondiente a este catálogo (CatalogoCarreras) se encargará de
manejar el tamaño de la clave y el tamaño del nombre de carreras. Por el momen-
to supondremos que la clase es una clase que nos proporciona el sistema. Para
evitarnos problemas, leeremos este catálogo de un archivo en disco –en el capı́tulo
en que veamos manejo de excepciones y archivos la destaparemos–. La definición
de la clase se encuentra en el Listado 5.2, donde únicamente aparecen los enca-
bezados y la descripción de los métodos. Por supuesto que únicamente aparece lo
que es accesible desde fuera, que es lo de acceso público.
En el caso del catálogo de carreras se decidió no definir una interfaz pues
todos los métodos y atributos son estáticos a la clase, por lo que no será necesario
construir un objeto para manejarla.

Código 5.2 Definición de la clase CatalogoCarreras CatalogoCarreras (1/2)

1 package C o n s u l t a s L i s t a s ;
2 import j a v a . i o . ∗ ;
3 import j a v a . u t i l . S c a n n e r ;
4 /∗ ∗
5 ∗ C l a s s <code>C a t a l o g o C a r r e r a s </code> c a r g a a memoria e l c a t a l o g o de
6 ∗ carreras .
7 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a . f c i e n c i a s . unam . mx”></a>
8 ∗ @version 1.0
9 ∗/
10 public class CatalogoCarreras {
11 /∗ ∗
12 ∗ C o n s t a n t e <code>TAM CLAVE</code> p a r a d a r e l tamaño de l a s
13 ∗ c l a v e s de c a r r e r a .
14 ∗/
15 p u b l i c s t a t i c f i n a l i n t TAM CLAVE = 3 ;
5.1 La clase para cada registro (estudiante) 192

Código 5.2 Definición de la clase CatalogoCarreras CatalogoCarreras (2/2)


35 /∗ ∗
36 ∗ C o n s t a n t e <code>TAM NOMBRE</code> p a r a d a r e l tamaño de l o s
37 ∗ nombres de c a r r e r a .
38 ∗/
39 p u b l i c s t a t i c f i n a l i n t TAM NOMBRE = 3 6 ;
40
41 /∗ ∗
42 ∗ Método <code>g e t C a r r e r a s </code> r e g r e s a una c a d e n a con l a s
43 ∗ claves .
44 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
45 ∗/
46 public static String getCarreras () {
...
89 }
90
91 /∗ ∗
92 ∗ Método <code>g e t S C a r r e r a s </code> r e g r e s a una c a d e n a con l o s
93 ∗ nombres .
94 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
95 ∗/
96 public static String getSCarreras () {
...
98 }
99
100 /∗ ∗
101 ∗ Método <code>d a C a r r e r a </code >, dada l a c l a v e de l a c a r r e r a
102 ∗ r e g r e s a e l nombre .
103 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >.
104 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
105 ∗/
106 public s t a t i c S t r i n g daCarrera ( i n t c u a l ){
...
121 }
122
123 /∗ ∗
124 ∗ Método <code>m u e s t r a C a t a l o g o </code> m u e s t r a e l c a t á l o g o de l a s
125 ∗ carreras .
126 ∗/
127 public s t a t i c void muestraCatalogo ( ) {
...
145 }
146
147 /∗ ∗
148 ∗ Método <code>e s C a r r e r a </code> d i c e s i l a c l a v e p r o p o r c i o n a d a e s
149 ∗ v á l i d a .
150 ∗ @param c u a l v a l o r de t i p o <code>i n t </code> c l a v e p r o p o r c i o n a d a .
151 ∗ @ r e t u r n v a l o r de t i p o <code>b o o l e a n </code >.
152 ∗/
153 p u b l i c s t a t i c boolean e s C a r r e r a ( i n t c u a l ) {
...
165 }
166 }
193 Datos estructurados

Una vez que logramos separar del estudiante el manejo del catálogo de carreras,
y utilizando a este último, procedemos a programar la clase para los registros de los
estudiantes, a la que llamaremos Estudiante. Veamos por lo pronto los atributos
(datos) que tiene esta clase en el listado 5.3. Lo primero que aparece son unas
constantes, que en este paquete me indican el tamaño con el que quiero editar a
cada uno de los campos, los enteros correspondientes a las carreras y los códigos
correspondientes a las mismas. Todas estas constantes deben ser compartidas por
todos los objetos que se construyan de esta clase, pues son las mismas p[ara todos
los objetos, por lo que están declaradas estáticas. En esta ocasión ya no es necesario
mantener a los campos con blancos a la derecha, pues como está cada uno en su
atributo, el tamaño de uno no influye en la localización del otro: accedemos a ellos
por el nombre del atributo, no por su posición en el registro. También vale la pena
notar que no importa el orden en el que listamos los atributos o campos, pues nos
vamos referir a ellos por su identificador.

Código 5.3 Atributos de la clase Estudiante Estudiante (1/2)

100 package C o n s u l t a s L i s t a s ;
200 import j a v a . u t i l . S c a n n e r ; // Para l e e r d a t o s
300 import u t i l e s . Cadenas ; // Métodos p a r a e d i t a r campos
400 import u t i l e s . C a t a l o g o C a r r e r a s ; // C a t á l o g o de c a r r e r a s
500 /∗ ∗
600 ∗ C l a s e <code>E s t u d i a n t e </code> r e p r e s e n t a a un r e g i s t r o
700 ∗ de l a b a s e de d a t o s .
800 ∗ Base de d a t o s , b a s a d a en l i s t a s de r e g i s t r o s , que emula l a
900 ∗ l i s t a de un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s
1000 ∗ n o r m a l e s de una b a s e de d a t o s y f u n c i o n a m e d i a n t e un Menú
1100 ∗ C r e a t e d : Wed Apr 22 0 8 : 4 6 : 5 3 2009
1200 ∗ @ a u t h o r <a>E l i s a V i s o </a>
1300 ∗ @version 2.0
1400 ∗/
1500 p u b l i c c l a s s E s t u d i a n t e implements S e r v i c i o s E s t u d i a n t e {
1600 /∗ Para e d i c i ó n d e l r e g i s t r o ∗/
1700 p u b l i c s t a t i c f i n a l i n t TAM NMBRE = 3 6 , // Para m o s t r a r
1800 TAM CTA = 9 , // Para m o s t r a r
1900 TAM CORREO = 2 0 , // Para m o s t r a r
2000 TAM CARRERA = C a t a l o g o C a r r e r a s .TAM NOMBRE; // Para m o s t r a r
2100 /∗ A t r i b u t o s de l a c l a s e ∗/
2200 p r i v a t e S t r i n g nombre ; /∗ ∗ Nombre d e l e s t u d i a n t e . ∗/
2300 private S tr i n g cuenta ; /∗ ∗ Número de c u e n t a d e l e s t u d i a n t e . ∗/
2400 private int carrera ; /∗ ∗ C a r r e r a que c u r s a . ∗/
2500 private String correo ; /∗ ∗ C o r r e o e l e c t r ó n i c o . ∗/
2600 p r i v a t e E s t u d i a n t e s i g u i e n t e ; /∗ ∗ R e f e r e n c i a a l r e s t o de
2700 l a l i s t a . ∗/
5.1 La clase para cada registro (estudiante) 194

Código 5.3 Atributos de la clase Estudiante Estudiante (2/2)

2800 /∗ ∗ C o n s t a n t e s que i d e n t i f i c a n a l o s campos ∗/


2900 public static f i n a l int
3000 NOMBRE = 1 ,
3100 CUENTA = 2 ,
3200 CARRERA = 3 ,
3300 CORREO = 4 ;

Mantenemos el tipo que dimos a los campos en la implementación anterior,


excepto por la carrera que la tenemos ahora como un campo tipo int; el atributo
siguiente es una referencia al siguiente de la lista (a la lista que le sigue), se refiere
a otro Estudiante, y lo denotamos dándole el tipo de la clase, y que, como todas las
variables del tipo de alguna clase, constituyen (contienen, sus valores corresponden
a) referencias.
Todas las constantes lo son de la clase (static final). Eso quiere decir que podrán
ser accesibles desde cualquiera de los objetos y sólo existe una copia, la de la clase.
Debemos tener un constructor que inicialice a un objeto de esta clase con las
cadenas adecuadas. Como tenemos al menos un constructor –el que asigna valores
determinados a los campos–, si queremos uno sin parámetros también lo tenemos
que programar nosotros. Por último, queremos un constructor que haga una copia
de un objeto dado; en términos de computación a esto se le llama clonar, pero
por el momento no entraremos en esos detalles. Los constructores se encuentran
en el listado 5.4.
Código 5.4 Constructores para la clase Estudiante Estudiante (1/2)

350 /∗ ∗
360 ∗ Crea un e j e m p l a r nuevo de <code>E s t u d i a n t e </code>
370 ∗ todos l o s a t r i b u t o s nulos ( s i n cadenas ) .
380 ∗/
390 public Estudiante () {
400 nombre = c o r r e o = c u e n t a = "" ;
410 }
420
430 /∗ ∗
440 ∗ Crea un e j e m p l a r nuevo de <code>E s t u d i a n t e </code >, i n i c i a n d o
450 ∗ con l o s a r g u m e n t o s d a d o s .
460 ∗ @param nmbre t i p o <code>S t r i n g </code >: p a r a <code>nombre</code >.
470 ∗ @param c o r r e o t i p o <code>S t r i n g </code >: p a r a <code>c o r r e o </code >.
480 ∗ @param c u e n t a t i p o <code>S t r i n g </code >: p a r a <code>c u e n t a </code >.
490 ∗ @param c a r r e r a t i p o <code>i n t </code >: p a r a <code>c a r r e r a </code >.
500 ∗/
195 Datos estructurados

Código 5.4 Constructores para la clase Estudiante Estudiante (2/2)

510 p u b l i c E s t u d i a n t e ( S t r i n g nmbre , S t r i n g c o r r e o ,
520 S t r i n g cuenta , i n t c a r r e r a ) {
530 nombre = nmbre == n u l l ? "" : nmbre ;
540 t h i s . c o r r e o = c o r r e o == n u l l ? "" : correo ;
550 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a ;
560 i f ( CatalogoCarreras . esCarrera ( carrera )) {
570 this . carrera = carrera ;
580 } // S i no l e ponemos nada queda en 0
590 }
600
610 /∗ ∗
620 ∗ Crea un e j e m p l a r nuevo de <code>E s t u d i a n t e </code >, c r e a n d o una
630 ∗ c o p i a d e l que s e p a s a como p a rá m e t r o , dando l u g a r d i s t i n t o en
640 ∗ e l heap .
650 ∗ @param c o p i a t i p o <code>E s t u d i a n t e </code> .
660 ∗/
670 public Estudiante ( Estudiante copia ) {
680 nombre = c o p i a . nombre ; c a r r e r a = c o p i a . c a r r e r a ;
690 cuenta = copia . cuenta ; correo = copia . correo ;
700 }

En la lı́nea 400 del listado 5.4 en la página opuesta inicializamos a los atributos
con las cadenas vacı́as. Esto lo hacemos con el fin de evitar que al tratar de
acceder a alguna de las cadenas el programa aborte porque la referencia sea nula.
El número de cuenta, por ser entero, se inicia en 0 y la referencia al resto de la
lista se inicia en null, lo que es correcto, pues a un estudiante aislado no le sigue
ninguna lista.
El constructor con parámetros –lı́neas 430 a 590– simplemente inicia al objeto
con los valores proporcionados. Nuevamente no es necesario asignar un valor a
siguiente, pues debe tener el valor de null, asignado por omisión. Únicamente ve-
rificaremos en el catálogo de carreras que la clave de la carrera exista – lı́neas 560
y 570–.
Por último, el constructor que hace un clon del objeto que se le pasa como
parámetro –lı́neas 610 a 700– produce un objeto nuevo al que copia, campo por
campo, el objeto pasado como parámetro.
Una vez que tenemos los constructores, tenemos que proveer, para cada campo
al que queramos que se tenga acceso, un método de consulta y uno de actuali-
zación. La programación de estos métodos se encuentra en el listado 5.5 en la
siguiente página.
5.1 La clase para cada registro (estudiante) 196

Código 5.5 Métodos de acceso y actualización de la clase Estudiante Estudiante (1/2)

7200 /∗ ∗
7300 ∗ Método <code>g e t C u e n t a </code >: o b t i e n e campo c o r r e s p o n d i e n t e .
7400 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: campo c o r r e s p o n d i e n t e .
7500 ∗/
7600 public S t r i n g getCuenta () {
7700 return cuenta ;
7800 }
7900
8000 /∗ ∗
8100 ∗ Método <code>s e t C u e n t a </code> a c t u a l i z a e l v a l o r
8200 ∗ de <code>c u e n t a </code >.
8300 ∗ @param newCuenta t i p o <code>S t r i n g </code>
8400 ∗ p a r a nuevo v a l o r .
8500 ∗/
8600 p u b l i c v o i d s e t C u e n t a ( S t r i n g newCuenta ) {
8700 t h i s . c u e n t a = newCuenta == n u l l ? "" : newCuenta ;
8800 }
8900
9000 /∗ ∗
9100 ∗ Método <code>g e t C a r r e r a </code> a c c e d e a l a t r i b u t o
9200 ∗ <code>c a r r e r a </code >.
9300 ∗ @ r e t u r n t i p o <code>i n t </code >: v a l o r d e l a t r i b u t o .
9400 ∗/
9500 public int getCarrera () {
9600 return c a r r e r a ;
9700 }
9800
9900 /∗ ∗
10000 ∗ Método <code>s e t C a r r e r a </code> a c t u a l i z a e l v a l o r de
10100 ∗ <code>c a r r e r a </code >. V e r i f i c a que s e a v á l i d o .
10200 ∗ @param n e w C a r r e r a t i p o <code>i n t </code> p a r a nuevo
10300 ∗ valor .
10400 ∗/
10500 public void s e t C a r r e r a ( i n t newCarrera ) {
10600 i f ( CatalogoCarreras . es Car rera ( newCarrera )) {
10700 th is . c a r r e r a = newCarrera ;
10800 } else {
10900 this . carrera = 0;
11000 }
11100 }
11200
11300 /∗ ∗
11400 ∗ Método <code>getNombre </code> a c c e d e a l a t r i b u t o
11500 ∗ <code>nombre</code >.
11600 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: v a l o r en <code>nombre</code >.
11700 ∗/
11800 p u b l i c S t r i n g getNombre ( ) {
11900 r e t u r n nombre ;
12000 }
197 Datos estructurados

Código 5.5 Métodos de acceso y actualización de la clase Estudiante Estudiante (2/2)

12200 /∗ ∗
12300 ∗ Método <code>setNombre </code> a c t u a l i z a e l v a l o r de
12400 ∗ <code>nombre</code >.
12500 ∗ @param newNombre t i p o <code>S t r i n g </code> p a r a
12600 ∗ nuevo v a l o r .
12700 ∗/
12800 p u b l i c v o i d setNombre ( S t r i n g newNombre ) {
12900 t h i s . nombre = newNombre == n u l l ? "" : newNombre ;
13000 }
13100
13200 /∗ ∗
13300 ∗ Método <code>g e t C o r r e o </code> a c c e d e a l a t r i b u t o
13400 ∗ <code>c o r r e o </code >.
13500 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: v a l o r en e l
13600 ∗ atributo .
13700 ∗/
13800 public String getCorreo () {
13900 return correo ;
14000 }
14100
14200 /∗ ∗
14300 ∗ Método <code>s e t C o r r e o </code >: A c t u a l i z a e l campo
14400 ∗ <code>c o r r e o </code >.
14500 ∗ @param newCorreo t i p o <code>S t r i n g </code>
14600 ∗ p a r a nuevo v a l o r .
14700 ∗/
14800 p u b l i c v o i d s e t C o r r e o ( S t r i n g newCorreo ) {
14900 t h i s . c o r r e o = newCorreo == n u l l ? "" : newCorreo ;
15000 }
15100
15200 /∗ ∗
15300 ∗ Método <code>g e t S i g u i e n t e </code> a c c e d e a l a t r i b u t o
15400 ∗ <code>s i g u i e n t e </code >.
15500 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >: r e f e r e n c i a a l
15600 ∗ s i g u i e n t e en l a l i s t a .
15700 ∗/
15800 public Estudiante getSiguiente () {
15900 return s i g u i e n t e ;
16000 }
16100
16200 /∗ ∗
16300 ∗ Método <code>s e t S i g u i e n t e </code >: a c t u a l i z a e l v a l o r de
16400 ∗ <code>s i g u i e n t e </code >.
16500 ∗ @param n e w S i g u i e n t e t i p o <code>E s t u d i a n t e </code>
16600 ∗ p a r a nuevo v a l o r .
16700 ∗/
16800 public void s e t S i g u i e n t e ( E s t u d i a n t e newSiguiente ) {
16900 this . s i g u i e n t e = newSiguiente ;
17000 }
5.1 La clase para cada registro (estudiante) 198

Noten que en los métodos en los que se espera una cadena, el método protege
al atributo para que siempre tenga un valor distinto de null, ya que si le pasan un
argumento que sea null lo sustituye por la cadena vacı́a.
El único método interesante es el que establece el código de la carrera, ya que
debe verificar que la clave de la carrera sea correcta; si no es ası́, coloca un 0 en
el campo correspondiente. Simplemente le pregunta al catálogo de carreras si la
clave es válida –lı́nea 10600 del listado 5.5–.
Podemos también poner métodos más generales para los atributos de la clase
Estudiante; podemos pedir al método que seleccione un campo a modificar o a
regresar. En el caso del atributo carrera que no es una cadena sino un entero,
podemos darle la cadena con el entero y que el método lo interprete. Los podemos
ver en el código 5.6.

Código 5.6 Métodos que actualizan y regresan un campo seleccionado Estudiante (1/2)

17200 /∗ ∗ Método <code>daCampo</code >: r e g r e s a e l campo s o l i c i t a d o .


17300 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >: s e l e c t o r d e l
17400 ∗ campo a r e g r e s a r .
17500 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >: e l c o n t e n i d o
17600 ∗ d e l campo e d i t a d o en una c a d e n a .
17700 ∗/
17800 p u b l i c S t r i n g daCampo ( i n t c u a l ) {
17900 switch ( c u a l ) {
18000 case NOMBRE: r e t u r n daNombre ( ) . t r i m ( ) ;
18100 case CUENTA : r e t u r n daCuenta ( ) . t r i m ( ) ;
18200 case CARRERA : return daCarrera ( ) . trim ( ) ;
18300 case CORREO : return daCorreo ( ) . trim ( ) ;
18400 default : r e t u r n " Número de campo inválido " ;
18500 }
18600 }
18700
18800 /∗ ∗
18900 ∗ Método <code>ponCampo</code >: a c t u a l i z a e l campo s o l i c i t a d o
19000 ∗ con l a i n f o r m a c i ó n p r o p o r c i o n a d a .
19100 ∗ @param c u a l t i p o <code>i n t </code> p a r a e l e g i r campo .
19200 ∗ @param v a l o r t i p o <code>S t r i n g </code> p a r a d a r e l nuevo v a l o r .
19300 ∗/
19400 p u b l i c v o i d ponCampo ( i n t c u a l , S t r i n g v a l o r ) {
19500 i f ( v a l o r == n u l l ) { // Para no m a n e j a r r e f e r e n c i a s n u l a s .
19600 v a l o r = "" ;
19700 }
19800 switch ( c u a l ) {
19900 case NOMBRE: setNombre ( v a l o r ) ;
20000 break ;
199 Datos estructurados

Código 5.6 Métodos que actualizan y regresan un campo seleccionado Estudiante (2/2)

20100 case CUENTA : setCuenta ( v a l o r ) ;


20200 break ;
20300 case CARRERA :
20400 int intValor = Integer . parseInt ( valor );
20500 i f ( CatalogoCarreras . esCarrera ( intValor )) {
20600 setCarrera ( intValor );
20700 } else {
20800 setCarrera (0);
20900 }
21000 break ;
21100 case CORREO : setCorreo ( valor );
21200 break ;
21300 default :
21400 System . o u t . p r i n t l n ( " Número de campo inválido " + c u a l ) ;
21500 }
21600 }

Dado que desearemos armar tablas (o listados) con los datos registrados para
cada estudiante y que cada registro tiene datos de tamaños distintos a los otros
registros, vamos a tener métodos que editen cada uno de los campos, con el prefijo
da –para distinguirlos de los que simplemente entregan el valor registrado en el
campo–. Para ello, requerimos un método que rellene el campo, ya sea por la
izquierda o por la derecha con un carácter especificado. El algoritmo se muestra
en la figura 5.2 y el código en el listado 5.7, Lo agregamos a una clase de manejo
de cadenas en nuestro paquete utiles que se llama Cadenas.

Figura 5.2 Algoritmo para rellenar un campo con un cierto carácter


$ "
'
'
'
' Inicialización i Ðtamaño de la
'
'
'
cadena
'
' "
'
'
'
' i ¥ tamaño Entrega subcadena con
'
'
' tamaño caracteres
'
'
'
'
` $ $ $ "
& '
'
' '
'
' '
' izquierda campo Ð carácter
Rellena '
' '
' '
&
un campo ' ' Agrega '
& carácter
agrega el
&carácter de + campo
'
'
' i   tamaño (i   tamaño
` "
'
' ' ' relleno ' '
' campo Ð campo
'
'
' '
' '
' %derecha
'
' '
'
'
del campo) '
'
'
+ carácter
'
'
' % %
'
'
'
Incrementa i
'
'
' !
% Final Entrega el campo rellenado
5.1 La clase para cada registro (estudiante) 200

Código 5.7 Método que rellena un campo con un carácter dado utiles.Cadenas

100 package u t i l e s ;
200
300 /∗ ∗
400 ∗ C l a s e <code>Cadenas </code> que r e ú n e v a r i o s métodos e s t á t i c o s p a r a
500 ∗ m a n i p u l a c i ó n de c a d e n a s .
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ v i s o ”> E l i s a V i s o G u r o v i c h </a>
700 ∗ @ v e r s i o n 1 . 0
800 ∗/
900 p u b l i c c l a s s Cadenas {
...
3300 /∗ ∗
3400 ∗ <code>r e l l e n a C a m p o </code> c o m p l e t a con e l c a r á c t e r i n d i c a d o l a
3500 ∗ c a d e n a s o l i c i t a d a a l tamaño i n d i c a d o .
3600 ∗
3700 ∗ @param c a d e n a t i p o <code>S t r i n g </code >: c a d e n a a r e l l e n a r .
3800 ∗ @param tamanho un <code>i n t </code >: tamaño a c o m p l e t a r .
3900 ∗ @param c a r t i p o <code>c h a r </code >: sı́ m b o l o p a r a r e l l e n a r .
4000 ∗ @param p o s i c i o n t i p o <code>c h a r </code >: ’ i ’ z q u i e r d a o ’ d ’ e r e c h a .
4100 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a d e n a r e l l e n a d a .
4200 ∗/
4300 p u b l i c s t a t i c S t r i n g r e l l e n a C a m p o ( S t r i n g cadena , i n t tamanho ,
4400 char c a r , char p o s i c i o n ) {
4500 i f ( c a d e n a == n u l l ) { // e s t á v a cı́ a
4600 c a d e n a = "" ;
4700 }
4800 i n t i = cadena . l e n g t h ( ) ;
4900 i f ( i > tamanho ) {
5000 c a d e n a = c a d e n a . s u b s t r i n g ( 0 , tamanho ) ;
5100 return cadena ;
5200 } else {
5300 i f ( i == tamanho ) {
5400 return cadena ;
5500 }
5600 }
5700 // Cadena e s < tamanho
5800 w h i l e ( i < tamanho ) {
5900 i f ( p o s i c i o n == ’i’ ) {
6000 cadena = c a r + cadena ;
6100 } else {
6200 cadena = cadena + c a r ;
6300 }
6400 i ++;
6500 }
6600 return cadena ;
6700 }
201 Datos estructurados

Ahora sı́ es fácil implementar los métodos que regresan los atributos en cadenas
rellenadas para que queden alineadas en una tabla. Estos se encuentran en el
listado 5.8.

Código 5.8 Regresa los campos ”rellenados” Estudiante

21800 /∗ ∗
21900 ∗ <code>daNombre</code> r e g r e s a una c a d e n a de l o n g i t u d
22000 ∗ <code>TAM NMBRE</code> r e l l e n a n d o con b l a n c o s a l a d e r e c h a .
22100 ∗ @ r e t u r n a <code>S t r i n g </code> v a l u e
22200 ∗/
22300 p u b l i c S t r i n g daNombre ( ) {
22400 r e t u r n Cadenas . r e l l e n a C a m p o ( nombre , TAM NMBRE, ’ ’ , ’d’ ) ;
22500 }
22600
22700 /∗ ∗
22800 ∗ <code>daCuenta </code> arma una c a d e n a de tamaño f i j o
22900 ∗ r e l l e n a n d o con c e r o s a l a i z q u i e r d a .
23000 ∗ @ r e t u r n t i p o <code>S t r i n g </code>
23100 ∗/
23200 p u b l i c S t r i n g daCuenta ( ) {
23300 r e t u r n Cadenas . r e l l e n a C a m p o ( c u e n t a , TAM CTA, ’0’ , ’i’ ) ;
23400 }
23500
23600 /∗ ∗
23700 ∗ <code>d a C a r r e r a </code> r e g r e s a e l nombre de l a c a r r e r a con un
23800 ∗ tamaño f i j o , r e l l e n a d o de b l a n c o s a l a d e r e c h a .
23900 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code>
24000 ∗/
24100 public String daCarrera () {
24200 S t r i n g nCarre = CatalogoCarreras . daCarrera ( c a r r e r a ) ;
24300 return nCarre ;
24400 }
24500
24600 /∗ ∗
24700 ∗ <code>d a C o r r e o </code> r e g r e s a una c a d e n a de tamaño f i j o con e l
24800 ∗ correo del estudiante s ol i ci t ad o .
24900 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code>
25000 ∗/
25100 public S t r i n g daCorreo () {
25200 r e t u r n Cadenas . r e l l e n a C a m p o ( c o r r e o , TAM CORREO, ’ ’ , ’d’ ) ;
25300 }

Finalmente, de esta clase únicamente nos faltan dos métodos, uno que regrese
todo el registro armado, listo para impresión, y uno que actualice todos los campos
del objeto. Podemos ver su implementación en el listado 5.9 en la siguiente página.
5.1 La clase para cada registro (estudiante) 202

Código 5.9 Métodos que arman y actualizan registro completo Estudiante

25500 /∗ ∗
25600 ∗ Método <code>a r m a R e g i s t r o </code >, r e g r e s a una c a d e n a con e l
25700 ∗ contenido del r e g i s t r o editado .
25800 ∗ @ r e t u r n t i p o <code>S t r i n g </code> c o r r e s p o n d i e n t e a l
25900 ∗ registro .
26000 ∗/
26100 public String armaRegistro () {
26200 r e t u r n daCuenta ( ) + " "
26300 + daCarrera () + " "
26400 + daNombre ( ) + " "
26500 + daCorreo ( ) ;
26600 }
26700
26800 /∗ ∗
26900 ∗ Método <code>p o n R e g i s t r o </code> a c t u a l i z a a t o d o e l r e g i s t r o .
27000 ∗ @param nmbre t i p o <code>S t r i n g </code> p a r a s u s t i t u i r
27100 ∗ a l nombre .
27200 ∗ @param c a r r e r a t i p o <code>i n t </code> p a r a s u s t i t u i r
27300 ∗ la carrera .
27400 ∗ @param c u e n t a t i p o <code>S t r i n g </code> p a r a s u s t i t u i r
27500 ∗ l a cuenta .
27600 ∗ @param c o r r e o t i p o <code>S t r i n g </code> p a r a s u s t i t u i r
27700 ∗ al correo .
27800 ∗/
27900 p u b l i c v o i d p o n R e g i s t r o ( S t r i n g nmbre , i n t c a r r e r a ,
28000 S t r i n g cuenta , S t r i n g c o r r e o ) {
28100 nombre = nmbre == n u l l ? "" : nmbre . t r i m ( ) ;
28200 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a . t r i m ( ) ;
28300 this . carrera = CatalogoCarreras . esCarrera ( carrera )
28400 ? carrera : 0;
28500 t h i s . c o r r e o = c o r r e o == n u l l ? "" : c o r r e o . t r i m ( ) ;
28600 }

Elegimos darle a estos cuatro últimos métodos nombres que no empiezan con
get o set, ya que Java reserva estos prefijos para métodos que trabajan con cada
campo por separado y lo regresan tal cual.
Como se puede observar, no hay necesidad de “diseñar” los métodos previo a
su programación, ya que son, en general, muy pequeños y concisos. Es importante
notar que en esta clase no interviene ningún método que maneje ningún tipo de
colección de objetos, sino únicamente lo que concierne a cada registro en sı́ mismo.
Queremos ver si nuestros métodos funcionan bien, por lo que agregamos tem-
poralmente un método main que pruebe los distintos métodos. Para ello usaremos
constantes y no leeremos datos de la consola. El método main –listado 5.10–, y los
resultados obtenidos de la ejecución se muestran a continuación –figura 5.3 en la
página 204–.
203 Datos estructurados

Código 5.10 Prueba de la clase Estudiante Estudiante (1/3)

28800 /∗ Prueba de l o s métodos de l a c l a s e ∗/


28900 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
29000 CatalogoCarreras . muestraCatalogo ( ) ;
29100 System . o u t . p r i n t l n ( " Probando a la clase Estudiante ..." ) ;
29200 S c a n n e r c o n s = new S c a n n e r ( System . i n ) ;
29300
29400 E s t u d i a n t e yo = new E s t u d i a n t e ( ) ;
29500 System . o u t . p r i n t l n ( "El registro ’yo ’ contiene a:\n"
29600 + " Nombre :\t*" + yo . daNombre ( ) + "*\n"
29700 + " Carrera :\t*" + yo . d a C a r r e r a ( ) + "*\n"
29800 + " Cuenta :\t*" + yo . daCuenta ( ) + "*\n"
29900 + " Correo :\t*" + yo . d a C o r r e o ( ) +"*" ) ;
30000 yo . setNombre ( " Elisa Viso" ) ;
30100 yo . s e t C a r r e r a ( 2 0 1 ) ;
30200 yo . s e t C u e n t a ( " 067302682 " ) ;
30300 yo . s e t C o r r e o ( " elisa@ciencias " ) ;
30400
30500 System . o u t . p r i n t l n ( " Después de la asignación , el registro ’yo ’"
30600 + " contiene a:\n"
30700 + " Nombre :\t*" + yo . daNombre ( ) + "*\n"
30800 + " Carrera :\t*" + yo . d a C a r r e r a ( ) + "*\n"
30900 + " Cuenta :\t*" + yo . daCuenta ( ) + "*\n"
31000 + " Correo :\t*" + yo . d a C o r r e o ( ) +"*\n"
31100 + "y el registro completo es :\n*"
31200 + yo . a r m a R e g i s t r o ( ) + "*" ) ;
31300 E s t u d i a n t e t u = new E s t u d i a n t e ( " Perico de los palotes " ,
31400 " periquito@unam " ,
31500 " 20109090909090 " ,
31600 yo . g e t C a r r e r a ( ) ) ;
31700 System . o u t . p r i n t l n ( "El registro ’tu ’ contiene a:\n"
31800 + " Nombre :\t*" + t u . daNombre ( ) + "*\n"
31900 + " Carrera :\t*" + t u . d a C a r r e r a ( ) + "*\n"
32000 + " Cuenta :\t*" + t u . daCuenta ( ) + "*\n"
32100 + " Correo :\t*" + t u . d a C o r r e o ( ) + "*\n"
32200 + "y el registro completo es :\n*"
32300 + t u . a r m a R e g i s t r o ( ) + "*" ) ;
32400 t u . p o n R e g i s t r o ( t u . getNombre ( ) , 1 0 1 , yo . g e t C u e n t a ( ) ,
32500 tu . getCorreo ( ) ) ;
32600 t u . s e t C u e n t a ( " 35000 " ) ;
32700 System . o u t . p r i n t l n ( "yo. getCuenta ()="+yo . g e t C u e n t a ( ) ) ;
32800 System . o u t . p r i n t l n ( " Después de la asignación , el registro ’tu ’"
32900 + " contiene a:\n"
33000 + " Nombre :\t*" + t u . daNombre ( ) + "*\n"
33100 + " Carrera :\t*" + t u . d a C a r r e r a ( ) + "*\n"
33200 + " Cuenta :\t*" + t u . daCuenta ( ) + "*\n"
33300 + " Correo :\t*" + t u . d a C o r r e o ( ) + "*\n"
33400 + "y el registro completo es :\n*"
33500 + t u . a r m a R e g i s t r o ( ) + "*" ) ;
5.1 La clase para cada registro (estudiante) 204

Código 5.10 Prueba de la clase Estudiante Estudiante (3/3)

33600 /∗ Armamos una peque ña l i s t a ” a p i e ” ∗/


33700 E s t u d i a n t e m i L i s t a = tu ;
33800 t u . s e t S i g u i e n t e ( yo ) ;
33900 t u . g e t S i g u i e n t e ( ) . s e t S i g u i e n t e ( new E s t u d i a n t e ( " Papacito " ,
34000 " yo@ciencias " ,
34100 "0347" ,
34200 122));
34300 tu . g e t S i g u i e n t e ( ) . g e t S i g u i e n t e ( ) .
34400 s e t S i g u i e n t e ( new E s t u d i a n t e ( " Mamacita " , " mami@ciencias " ,
34500 "0348" , 1 2 7 ) ) ;
34600 /∗ Veamos a h o r a e l c o n t e n i d o de l a l i s t a ∗/
34700 System . o u t . p r i n t l n ( "\n **************************\ n"
34800 + "El contenido de la lista es :\n" ) ;
34900 Estudiante actual = miLista ;
35000 w h i l e ( a c t u a l != n u l l ) {
35100 System . o u t . p r i n t l n ( a c t u a l . a r m a R e g i s t r o ( ) ) ;
35200 actual = actual . getSiguiente ();
35300 }
35400 }

Figura 5.3 Ejecución de prueba de la clase Estudiante (1/2)

1 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
2 ===============================================
3 101 Actuaria
4
5 104 C i e n c i a s de l a Computación
6
7 106 Fı́ s i c a
8
9 122 M a t e má t i c a s
10
11 127 C i e n c i a s de l a T i e r r a
12
13 201 B i o l o gı́ a
14
15 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
16
17 =========================================================
18 Probando a l a c l a s e E s t u d i a n t e . . .
19 E l r e g i s t r o ’yo ’ c o n t i e n e a :
20 Nombre : ∗ ∗
21 C a r r e r a : ∗ C ó d i g o i n v a l i d o ∗
22 Cuenta : ∗000000000∗
23 Correo : ∗ ∗
24 Después de l a a s i g n a c i ó n , e l r e g i s t r o ’yo ’ c o n t i e n e a :
25 Nombre : ∗ E l i s a V i s o ∗
26 C a r r e r a : ∗ B i o l o gı́ a ∗
205 Datos estructurados

Figura 5.3 Ejecución de prueba de la clase Estudiante (2/2)

27 Cuenta : ∗067302682∗
28 Correo : ∗ e l i s a @ c i e n c i a s ∗
29 y e l r e g i s t r o completo es :
30 ∗067302682 B i o l o gı́ a E l i s a Viso
31 elisa@ciencias ∗
32 E l r e g i s t r o ’tu ’ c o n t i e n e a :
33 Nombre : ∗ P e r i c o de l o s p a l o t e s ∗
34 C a r r e r a : ∗ B i o l o gı́ a ∗
35 Cuenta : ∗201090909∗
36 Correo : ∗ periquito@unam ∗
37 y e l r e g i s t r o completo es :
38 ∗201090909 B i o l o gı́ a P e r i c o de
39 los palotes periquito@unam ∗
40 yo . g e t C u e n t a ()=067302682
41 Después de l a a s i g n a c i ó n , e l r e g i s t r o ’tu ’ c o n t i e n e a :
42 Nombre : ∗ P e r i c o de l o s p a l o t e s ∗
43 Carrera : ∗ Actuaria ∗
44 Cuenta : ∗000035000∗
45 Correo : ∗ periquito@unam ∗
46 y e l r e g i s t r o completo es :
47 ∗000035000 A c t u a r i a P e r i c o de
48 los palotes periquito@unam ∗
49
50 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
51 E l c o n t e n i d o de l a l i s t a e s :
52
53 000035000 A c t u a r i a P e r i c o de
54 los palotes periquito@unam
55 067302682 B i o l o gı́ a E l i s a Viso
56 elisa@ciencias
57 000000347 B i o l o gı́ a Papacito
58 yo@ciencias
59 000000348 C i e n c i a s de l a T i e r r a Mamacita
60 mami@ciencias

En la figura anterior la numeración de las lı́neas no forma parte de lo que


produce la aplicación, sino que está puesta para facilitar la discusión. Los sı́mbolos
“ ” y “ ” marcan lı́neas que se “desbordan” en la pantalla.
Podemos seguir paso a paso la creación de objetos, recordando que los identi-
ficadores de objetos contienen, en ejecución, referencias (direcciones) al lugar en
el heap en el que quedan localizados los objetos, mientras que las variables que
se refieren a valores primitivos (int, long, byte, short, float, double, boolean y char)
ocupan lugar directamente en la memoria de ejecución –trabajamos directamente
con el contenido de la memoria asignada–.
Después de ejecutar la lı́nea 29400 la memoria queda como se muestra en la
figura 5.4 en la siguiente página.
5.1 La clase para cada registro (estudiante) 206

Figura 5.4 Memoria hasta la lı́nea 29400

Variables Heap
nombre cuenta carrera correo signte

(4086)
(8126) (84322)
0 (3010)
[]
yo (4086) ”” (cadena vacı́a)
(3010)

”” (cadena vacı́a)
(84322)

”” (cadena vacı́a)
(8126)

En esta figura, los enteros entre paréntesis corresponden a direcciones “fı́sicas”


en la memoria: en la variable tipo referencia yo se almacena la dirección en memoria
donde empieza el registro al que apunta, o bien el valor null si es que no tiene
espacio asignado en el heap. Cabe aclarar que el programador nunca tiene acceso
a las direcciones absolutas de memoria, sino únicamente a través de las variables
–se ilustra sólo para que quede clara la asignación de espacio en el heap–.
En las lı́neas 30000 a 30300 se modifica el contenido de los campos, quedando
la memoria como se muestra en la figura 5.5.

Figura 5.5 Memoria hasta la lı́nea 30300

Variables Heap
nombre cuenta carrera correo signte

(4086)
(32100) (40800)
201 (3198)
[]
yo (4086)
“elisa@ciencias”
(3198)
“067302682”
(40800)

“Elisa Viso”
(32100)

En las lı́neas 31300 a 31600 tenemos la declaración y construcción de otro


objeto de la clase Estudiante, quedando la memoria como se muestra en la figura 5.6
en la página opuesta.
207 Datos estructurados

Figura 5.6 Memoria hasta la lı́nea 31600

Variables Heap
nombre cuenta carrera correo signte

(4086)
(40800) (32100)
201 (3198)
[]
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”

nombre cuenta carrera correo signte

(75300)
(7610) (3280)
201 (5432)
[]
tu (75300)
(5432)
“periquito@unam”
(3280) “20109090909090”
(7610)
“Perico de los palotes”

En las lı́neas 32400 a 32600 se modifica el contenido (el estado) del objeto
referido por tu, quedando la memoria como se muestra en la figura 5.7. Hay que
notar que las direcciones de memoria en el heap para las cadenas contenidas en
el objeto referido por tu no coinciden con las cadenas referidas por el objeto yo
aunque la cadena sea la misma: se creó una cadena nueva y se le asignó una
nueva dirección en el heap. Esto sucede con las cadenas, ya que no pueden ser
modificadas (son objetos constantes).

Figura 5.7 Memoria hasta la lı́nea 32600


Variables Heap
nombre cuenta carrera correo signte

(4086)
(40800) (32100)
201 (3198)
[]
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”

nombre cuenta carrera correo signte

(75300)
(2500) (8200)
101 (5470)
[]
tu (75300)
(5470)
“periquito@unam”
(8200) “067302682”
(2500)
“Perico de los palotes”
5.2 La lista de registros 208

A partir de la lı́nea 33700 probamos la construcción de listas ligadas. Después


de la ejecución de la lı́nea 33800, la memoria se encuentra como se muestra en la
figura 5.8.

Figura 5.8 Memoria hasta la lı́nea 33800

Variables Heap
nombre cuenta carrera correo signte

(4086)
(40800) (32100)
201 (3198)
[]
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”
nombre cuenta carrera correo signte
(2500) (8200) (5470) (4086)
(75300) 101
tu (75300)
(5470)
“periquito@unam”
(8200) “067302682”
(2500)
“Perico de los palotes”

(75300)

miLista

En las lı́neas 33900 a 34500 se crean dos nuevos registros y se enlazan a la


lista referida por miLista – que es la misma que empieza en tu –. El estado de la
memoria se muestra en la figura 5.9 en la página opuesta.
Una vez comprobado que la clase trabaja bien eliminamos de nuestra clase al
método main.

5.2 La lista de registros

Como acabamos de mencionar, la interrelación entre los distintos registros de


nuestra base de datos la vamos a manejar desde una clase separada a la de los
registros mismos, la lista de registros, que deberá encontrarse nuevamente en una
clase dedicada al grupo.
209 Datos estructurados

Figura 5.9 Memoria hasta la lı́nea 34500

Variables Heap
nombre cuenta carrera correo signte
(8788) (9876)
127 (7676)
[]
(4444)
(7676)
“mami@ciencias”
(9876) “0348”
(8788) “Mamacita”
nombre cuenta carrera correo signte
(8768) (6464) (5432) (4444)
122
(6688)
(5432)
“yo@ciencias”
(6464) “0347”
(8768)
“Papacito”
nombre cuenta carrera correo signte
(40800) (32100) (3198) (6688)
(4086) 201
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”
nombre cuenta carrera correo signte
(2500) (8200) (5470) (4086)
(75300) 101
tu (75300)
(5470)
“periquito@unam”
(8200) “067302682”
(2500)
“Perico de los palotes”

(75300)

miLista

Antes de entrar de lleno a la implementación de la lista de registros es conve-


niente regresar al contrato que queremos establecer con la lista de los cursos. La
tarjeta de responsabilidades se parece mucho a la que hicimos en el capı́tulo an-
terior y debe tener los siguientes servicios, que son prácticamente los mismos que
en el caso de la base de datos guardada en una cadena –ver figura 5.10– excepto
que cambiamos algunos nombres de métodos y el tipo del valor que regresan.
5.2 La lista de registros 210

Figura 5.10 Tarjeta de responsabilidades para Curso.


Clase: Curso Responsabilidades
Constructores A partir de una base de datos inicial y a partir de
cero.
Métodos relativos al estudiante
P daNombre Regresa el nombre completo del estudiante solicitado
daCarrera Regresa la carrera del estudiante solicitado
ú daCorreo Regresa la clave de acceso del estudiante solicitado
daCuenta Regresa el número de cuenta del estudiante solicitado
b agregaEstudiante Agrega a un estudiante, proporcionando los datos co-
l rrespondientes.
armaRegistro Regresa el registro “bonito” para imprimir
i Métodos relativos a la base de datos:
c localizaAlumno Localiza al alumno en la base de datos
agregaAlumno Agrega a un estudiante a la lista
o eliminaAlumno Elimina al estudiante de la lista
dameLista Lista todos los estudiantes del curso
dameCurso Regresa el listado del curso
losQueCazanCon Lista a los estudiantes que cazan con algún criterio
especı́fico
Con estas responsabilidades procedemos a codificar la interfaz para este modelo
de base de datos en el listado 5.11.Ponemos el encabezado anterior (comentado)
para los casos en que sustituimos un entero por una referencia.

Código 5.11 Interfaz para los cursos implementados con listas ServiciosCursoLista (1/4)

1 package C o n s u l t a s L i s t a s ;
2 /∗ ∗
3 ∗ I n t e r f a c e S e r v i c i o s C u r s o L i s t a : l i s t a l a s r e s p o n s a b i l i d a d e s de
4 ∗ una b a s e de d a t o s p a r a cada c u r s o en l a F a c u l t a d de C i e n c i a s .
5 ∗ Cre ad a : A b r i l 2 0 1 0 .
6 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ t u r i n g ”> E l i s a V i s o </a>
7 ∗ @version 2.0
8 ∗/
9 public interface S e r vi c i os Cu r s oL i sta {
10 /∗ ∗
11 ∗ Método <code>getGrupo </code >: r e g r e s a e l campo con e l número
12 ∗ de g r u p o .
13 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l a t r i b u t o d e l g r u p o .
14 ∗/
15 S t r i n g getGrupo ( ) ;
211 Datos estructurados

Código 5.11 Interfaz para los cursos implementados con listas ServiciosCursoLista (2/4)

17 /∗ ∗
18 ∗ Método <code>daNombre</code >: r e g r e s a e l nombre d e l
19 ∗ estudiante solicitado .
20 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
21 ∗ e s t u d i a n t e que s e d e s e a .
22 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l nombre d e l e s t u d i a n t e
23 ∗ solicitado .
24 ∗/
25 // S t r i n g daNombre ( i n t c u a l ) ;
26 S t r i n g daNombre ( E s t u d i a n t e c u a l ) ;
27
28 /∗ ∗
29 ∗ Método <code>d a C a r r e r a </code >: r e g r e s a e l nombre de l a c a r r e r a
30 ∗ del estudiante solicitado .
31 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
32 ∗ e s t u d i a n t e que s e d e s e a .
33 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l nombre de l a c a r r e r a d e l
34 ∗ estudiante solicitado .
35 ∗/
36 // S t r i n g d a C a r r e r a ( i n t c u a l ) ;
37 String daCarrera ( Estudiante cual ) ;
38
39 /∗ ∗
40 ∗ Método <code>d a C o r r e o </code >: : r e g r e s a e l c o r r e o d e l
41 ∗ estudiante solicitado .
42 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
43 ∗ e s t u d i a n t e que s e d e s e a .
44 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l c o r r e o d e l e s t u d i a n t e
45 ∗ solicitado .
46 ∗/
47 // S t r i n g d a C o r r e o ( i n t c u a l ) ;
48 S t r i n g daCorreo ( Estudiante cual ) ;
49
50 /∗ ∗
51 ∗ Método <code>daCuenta </code >: : r e g r e s a e l número de c u e n t a
52 ∗ del estudiante solicitado .
53 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
54 ∗ e s t u d i a n t e que s e d e s e a .
55 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l número de c u e n t a d e l
56 ∗ estudiante solicitado .
57 ∗/
58 // S t r i n g daCuenta ( i n t c u a l ) ;
59 S t r i n g daCuenta ( E s t u d i a n t e c u a l ) ;
5.2 La lista de registros 212

Código 5.11 Interfaz para los cursos implementados con listas ServiciosCursoLista (3/4)

61 /∗ ∗
62 ∗ Método <code>l o c a l i z a A l u m n o </code >: l o c a l i z a a l alumno que
63 ∗ t i e n e a l a s u b c a d e n a d e n t r o de a l g u n o de l o s campos .
64 ∗ @param c a d e n a t i p o <code>S t r i n g </code >: l a s u b c a d e n a a b u s c a r .
65 ∗ @param campo t i p o <code>i n t </code >: E l campo d e n t r o d e l
66 ∗ r e g i s t r o donde s e va a b u s c a r .
67 ∗ @param d e s d e t i p o <code>E s t u d i a n t e </code >: b u s c a r a p a r t i r de
68 ∗ e s t e r e g i s t r o en l a l i s t a .
69 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
70 ∗ r e g i s t r o donde s e l o c a l i z ó l a s u b c a d e n a en e l campo
71 ∗ solicitado .
72 ∗/
73 // E s t u d i a n t e l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ,
74 // i n t desde ) ;
75 E s t u d i a n t e l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ,
76 Estudiante desde ) ;
77
78 /∗ ∗
79 ∗ Método <code>l o c a l i z a A l u m n o </code> que t i e n e a l a s u b c a d e n a
80 ∗ d e n t r o de a l g u n o de l o s campos . b u s c a a p a r t i r de un
81 ∗ determinado r e g i s t r o .
82 ∗ @param c a d e n a t i p o <code>S t r i n g </code >: l a s u b c a d e n a a b u s c a r .
83 ∗ @param campo t i p o <code>i n t </code >: E l campo d e n t r o d e l
84 ∗ r e g i s t r o donde s e va a b u s c a r .
85 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a a l
86 ∗ r e g i s t r o donde s e l o c a l i z ó l a s u b c a d e n a en e l campo
87 ∗ solicitado .
88 ∗/
89 E s t u d i a n t e l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ) ;
90
91 /∗ ∗
92 ∗ Método <code>agregaAlumno </code >: Agrega a un alumno a l
93 ∗ p r i n c i p i o de l a b a s e de d a t o s .
94 ∗ @param n t i p o <code>S t r i n g </code >: nombre d e l alumno .
95 ∗ @param ca t i p o <code>i n t </code >: C l a v e de l a c a r r e r a .
96 ∗ @param c t a t i p o <code>S t r i n g </code >: número de c u e n t a .
97 ∗ @param co t i p o <code>S t r i n g </code >: c o r r e o .
98 ∗/
99 v o i d agregaAlumno ( S t r i n g n , i n t ca , S t r i n g c t a , S t r i n g co ) ;
100
101 /∗ ∗
102 ∗ Método <code>e l i m i n a A l u m n o </code >: E l i m i n a a l alumno i n d i c a d o
103 ∗ de l a b a s e de d a t o s . A v i s a s i l o pudo e l i m i n a r .
104 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: l a r e f e r e n c i a d e l
105 ∗ alumno a e l i m i n a r .
106 ∗/
107 // v o i d e l i m i n a A l u m n o ( i n t c u a l ) ;
108 boolean e l i m i n a A l u m n o ( E s t u d i a n t e c u a l ) ;
213 Datos estructurados

Código 5.11 Interfaz para los cursos implementados con listas ServiciosCursoLista (4/4)

110 /∗ ∗
111 ∗ Método <code>a r m a R e g i s t r o </code >: Arma l a c a d e n a p a r a m o s t r a r
112 ∗ el registro del estudiante indicado .
113 ∗ @param c u a l t i p o <code>E s t u d i a n t e </code >: E l e s t u d i a n t e
114 ∗ solicitado .
115 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: La c a d e n a con e l r e g i s t r o
116 ∗ editado .
117 ∗/
118 // S t r i n g a r m a R e g i s t r o ( i n t c u a l ) ;
119 String armaRegistro ( Estudiante cual ) ;
120
121 /∗ ∗
122 ∗ Método <code>d a m e L i s t a </code >: L i s t a c o m p l e t a e d i t a d a como
123 ∗ tabla .
124 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: l a l i s t a e d i t a d a .
125 ∗/
126 S t r i n g dameLista ( ) ;
127
128 /∗ ∗
129 ∗ Método <code>dameCurso </code >: L i s t a c o m p l e t o e l c u r s o .
130 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: La c a d e n a con e l c u r s o
131 ∗ editado .
132 ∗/
133 S t r i n g dameCurso ( ) ;
134
135 /∗ ∗
136 ∗ método <code>losQueCazanCon </code >: Arma una c a d e n a con l o s
137 ∗ r e g i s t r o s de l o s e s t u d i a n t e s que cumplen con e l c r i t e r i o
138 ∗ solicitado .
139 ∗ @param s u b c a d t i p o <code>S t r i n g </code >: Subcadena a b u s c a r .
140 ∗ @param campo t i p o <code>i n t </code >: campo donde b u s c a r .
141 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: Cadena con l a l i s t a de
142 ∗ registros solicitados .
143 ∗/
144 S t r i n g losQueCazanCon ( S t r i n g subcad , i n t campo ) ;
145 } // S e r v i c i o s C u r s o L i s t a

La diferencia fundamental entre esta interfaz y la que dimos en el capı́tulo


anterior es que ahora identificamos a un estudiante por su referencia (dirección en
el heap) y ya no por la posición relativa que ocupa en la lista.
Para toda lista requerimos, como mencionamos en relación con la figura 5.1
en la página 187, un “ancla” para la lista, algo que sea la referencia al primer
elemento de la lista. De no tener esta referencia, como la lista se va a encontrar en
el heap, no habrá manera de saber dónde empieza o dónde están sus elementos.
Hagan de cuenta que el heap es el mar y las declaraciones en una clase son un
barco. Cada vez que se crea un objeto, éste se acomoda en el heap (mar), por
5.2 La lista de registros 214

lo que tenemos que tener una cuerda (cadena, referencia, apuntador) desde el
barco hacia el primer objeto, de éste al segundo y ası́ sucesivamente. De esa
manera, si “jalamos la cuerda” podemos tener acceso a todos los elementos que
conforman la lista. “Jalar la cuerda” quiere decir, en este contexto, ir tomando
referencia por referencia, cada una pegada a la siguiente, como si se tratara de
un collar de cuentas. Al igual que en nuestra implementación anterior usaremos
un identificador lista que nos indique el primer elemento de la lista y consiste
de la referencia (dirección en el heap) del primer elemento. Por supuesto que
esta lista estará vacı́a –la variable tendrá un valor null– en tanto no le agreguemos
ningún objeto del tipo Estudiante. Como las listas ligadas son estructuras de datos
de acceso secuencial resulta muy costoso agregar estudiantes al final de la lista,
pues tenemos que recorrer la lista para encontrar el final. Agregaremos entonces
una referencia al último de la lista para facilitar algunas operaciones sobre la
misma. Agregaremos también constantes simbólicas que identifiquen el atributo
que deseamos trabajar. Por lo tanto, las únicas diferencias entre las declaraciones
de nuestra implementación anterior y ésta es el tipo de la lista y la referencia al
último de la lista –en el caso de las cadenas no era necesario pues tenı́amos la
función length()– quedando las primeras lı́neas de la clase como se puede apreciar
en el listado 5.12.

Código 5.12 Atributos de la clase CursoListas CursoListas

100 package C o n s u l t a s L i s t a s ;
200 import u t i l e s . Cadenas ;
300 import u t i l e s . C a t a l o g o C a r r e r a s ;
400 /∗ ∗
500 ∗ C l a s e <code>C u r s o L i s t a s </code> maneja l a l i s t a de un g r u p o .
600 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ v i s o ”> E l i s a V i s o G u r o v i c h </a>
700 ∗ @version 1.2
800 ∗/
900 p u b l i c c l a s s C u r s o L i s t a s implements S e r v i c i o s C u r s o L i s t a {
1000 public static f i n a l int
1100 TAM GPO = 4 , // Núm p o s i c i o n e s en e l g r u p o
1200 NOMBRE = E s t u d i a n t e .NOMBRE, // I d d e l campo p a r a nombre
1300 CARRERA = E s t u d i a n t e . CARRERA, // I d d e l campo p a r a c a r r e r a
1400 CORREO = E s t u d i a n t e . CORREO, // I d d e l campo p a r a c o r r e o
1500 CUENTA = E s t u d i a n t e . CUENTA ; // I d d e l campo p a r a numero
1600 private S t r i n g grupo ; // Guarda número de g r u p o como c a d e n a
1700 p r i v a t e E s t u d i a n t e l i s t a ; // R e f e r e n c i a a l p r i n c i p i o de l a l i s t a
1800 // de e s t u d i a n t e s i n s c r i t o s en e l g r u p o
1900 p r i v a t e E s t u d i a n t e u l t i m o ; // R e f e r e n c i a a l ú l t i m o de l a l i s t a
2000 p r i v a t e i n t numRegs =  1; // Número de r e g i s t r o s en l a l i s t a ;
2100 // 1 i n d i c a que no ha s i d o i n i c i a l i z a d o
215 Datos estructurados

En el caso de esta clase es imprescindible que registremos el número de registros


que contiene la lista en un momento dado, pues el no hacerlo implica que cada vez
que requiramos de este dato nos veremos forzados a recorrer la lista contándolos.
Antes de seguir con la implementación de la clase, hagamos un paréntesis
para ver la forma general que tenemos de recorrer una lista. Si pensamos en una
lista secuencial, un elemento después del otro, el diagrama de Warnier que da el
algoritmo general para procesar listas se encuentra en la figura 5.11.
Figura 5.11 Algoritmo para procesar listas secuenciales
$ $
'
'
' &Colocarse al principio
'
'
' Inicialización de la lista
'
'
' %
'
'
&
Inicializar acumuladores
Proceso de #
lista secuencial ' Procesar registro Procesar registro actual
'
'
' (mientras haya o
'
'
' no se encuentre) Pasar al siguiente
'
'
' !
'
%Final Entregar los resultados

En la condición de iteración –(mientras haya o no se encuentre)– estamos in-


cluyendo también el caso de buscar elemento particular de la lista; si tenemos esta
situación, en cuanto encontremos al elemento buscado suspenderemos el recorrido
de la lista.
Regresamos a la implementación de la clase donde, al igual que con la clase
Curso, tenemos dos constructores, uno que únicamente coloca el número del gru-
po, y otro que inicializa la lista y coloca el número de grupo. También tenemos
un tercer constructor que copia (clona) el registro (no las referencias) creando los
campos en otra posición del heap. Debemos recordar que lo primero que hace todo
constructor es poner los valores de los atributos que corresponden a referencias
en null y los que corresponden a valores primitivos en 0, a menos que el atributo
tenga un enunciado de inicialización. Cuando se invoca al constructor que crea un
curso con una lista vacı́a de estudiantes tendremos que asignar 0 a numRegs, mien-
tras que si el constructor trabaja a partir de una lista inicial deberemos contar el
número de registros en esa lista inicial. Modificamos ligeramente el diagrama de
Warnier del proceso de una lista secuencial para que cuente los registros presen-
tes en la lista; extrayéndolo del algoritmo general dado tenemos lo siguiente: El
único acumulador que vamos a usar es el que cuenta los registros (contador); el
proceso del registro implica simplemente contarlo; entregar los resultados es dar
el valor final del contador. El diagrama de Warnier que resulta se puede ver en la
figura 5.12, que se encuentra a continuación.
5.2 La lista de registros 216

Figura 5.12 Cuenta de los registros de una lista


$ $ !
'
'
' &Colocarte al ini- actual Ð lista
'
'
'
' cio de la lista
'
'
'
Inicialización
' !
'
' %Inicia contador cuantos Ð 0
'
'
' $
& !
Cuenta registros '
'
& Incrementa cuantos
en una lista ' Cuenta el
'
'
' registro actual contador
!
'
'
' (mientras haya) ''
%
'
' Pasa al siguiente actual Ð toma el siguiente
'
'
'
'
' !
%Final Entrega el contador

La codificación del método que cuenta los registros presentes en la lista de


estudiantes se muestra a continuación en el listado 5.13. El método es privado
porque la base de datos es la que decide cuándo pedir que se cuenten los registros
y cuándo no.

Código 5.13 Método que cuenta los registros CursoListas

230 /∗ ∗
240 ∗ Método <code>c u e n t a R e g s </code >: c u e n t a e l número de r e g i s t r o s
250 ∗ p r e s e n t e s en l a l i s t a y a c t u a l i z a l a r e f e r e n c i a a l ú l t i m o .
260 ∗ @ r e t u r n t i p o <code>i n t </code >: número de r e g i s t r o s en l a
270 ∗ lista .
280 ∗/
290 private int cuentaRegs () {
300 int contador = 0;
310 Estudiante actual = l i s t a ;
320 w h i l e ( a c t u a l != n u l l ) {
330 c o n t a d o r ++;
340 /∗ Vemos s i e s e l ú l t i m o ∗/
350 i f ( a c t u a l . g e t S i g u i e n t e ( ) == n u l l ) {
360 ultimo = actual ;
370 }
380 actual = actual . getSiguiente ();
390 }
400 return contador ;
410 }

El grupo lo rellenaremos con ceros a la izquierda para garantizar homogenei-


dad, usando para ello el método rellenaCampo de la clase Cadenas en el paquete
utiles en nuestro subdirectorio de trabajo.
Regresando a los métodos constructores de la clase, ambos se encuentran en
el listado 5.14 en la página opuesta y hacen uso de los métodos privados que
217 Datos estructurados

acabamos de mostrar.

Código 5.14 Constructores para la clase CursoListas CursoListas


4300 /∗ ∗
4400 ∗ Crea un e j e m p l a r nuevo de <code>C u r s o L i s t a s </code >.
4500 ∗ @param g r u p o a de t i p o <code>S t r i n g </code >, da e l numero d e l
4600 ∗ grupo .
4700 ∗/
4800 public C u r s o L i s t a s ( S t r i n g grupo ) { %
4900 t h i s . g r u p o = Cadenas . r e l l e n a C a m p o ( grupo , TAM GPO, ’0’ , ’i’ ) ;
5000 l i s t a = null ;
5100 ultimo = null ;
5200 numRegs = 0 ;
5300 }
5400
5500 /∗ ∗
5600 ∗ Crea un e j e m p l a r nuevo de <code>C u r s o L i s t a s </code >.
5700 ∗ @param g r u p o de t i p o <code>S t r i n g </code >, e l numero de g r u p o .
5800 ∗ @param l i s t a I n i c i a l de t i p o <code>E s t u d i a n t e </code >, una l i s t a
5900 ∗ i n i c i a l de e s t u d i a n t e s .
6000 ∗/
6100 p u b l i c C u r s o L i s t a s ( S t r i n g grupo , E s t u d i a n t e l i s t a I n i c i a l ) {
6200 t h i s . g r u p o = Cadenas . r e l l e n a C a m p o ( grupo , TAM GPO, ’0’ , ’i’ ) ;
6300 lista = listaInicial ;
6400 numRegs = c u e n t a R e g s ( ) ;
6500 }

El segundo constructor tiene dos parámetros; el primero de ellos es una re-


ferencia a un objeto de la clase Estudiante y el segundo es una referencia a una
cadena. El constructor inicializa la lista con esta referencia (copia la referencia a
la variable lista) y asigna el número de grupo. Procede a contar los registros en la
lista e inicia el contador de registros con el número de registros contado.

Dado que en la clase CursoLista tenemos tres atributos, tenemos que definir
tres métodos de acceso a los atributos. Los tres métodos regresan simplemente el
valor del atributo. Los podemos ver en el listado 5.15 en la siguiente página.
5.2 La lista de registros 218

Código 5.15 Métodos que trabajan con los atributos del objeto CursoListas (1/2)
6700 /∗ ∗
6800 ∗ Método <code>getGrupo </code >, r e g r e s a d i r e c t a m e n t e l a c a d e n a
6900 ∗ a l m a c e n a d a p a r a e l numero d e l g r u p o . .
7000 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, que e s e l numero de
7100 ∗ grupo .
7200 ∗/
7300 public S t r i n g getGrupo ( ) {
7400 return grupo ;
7500 }

Código 5.15 Métodos que trabajan con los atributos del objeto CursoListas (1/2)
7700 /∗ ∗
7800 ∗ Método <code>g e t L i s t a </code> r e g r e s a l a r e f e r e n c i a a l p r i m e r o
7900 ∗ de l a l i s t a .
8000 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >.
8100 ∗/
8200 public Estudiante getLista () {
8300 return l i s t a ;
8400 }
8500
8600 /∗ ∗
8700 ∗ Método <code>getNumRegs</code >: r e g r e s a e l número de
8800 ∗ r e g i s t r o s en l a b a s e de d a t o s .
8900 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
9000 ∗/
9100 p u b l i c i n t getNumRegs ( ) {
9200 r e t u r n numRegs ;
9300 }

Junto con los métodos de acceso debemos tener métodos que alteren o asignen
valores a los atributos. Sin embargo, tanto el atributo numRegs como lista de-
berán ser modificados por las operaciones de la base de datos y no directamente.
En cambio, podemos querer cambiarle el número de grupo a una lista. Lo hace-
mos simplemente indicando cuál es el nuevo número de grupo. Podemos ver este
método en el listado 5.16 en la página opuesta.
219 Datos estructurados

Código 5.16 Modifica el número de grupo CursoListas

9500 /∗ ∗
9600 ∗ Método <code>s e t G r u p o </code >: a c t u a l i z a e l g r u p o .
9700 ∗ @param g r t i p o <code>S t r i n g </code >: nuevo v a l o r .
9800 ∗/
9900 public void setGrupo ( S t r i n g gr ) {
10000 g r u p o = ( g r == n u l l ) ? "" : g r ;
10100 }

La codificación de los métodos que regresan alguno de los campos editados de


un registro simplemente delegan en el registro para que sea éste el que entregue
el valor de sus campos. Mostramos el código correspondiente en el listado 5.17; el
algoritmo es secuencial y muy sencillo, por lo que no mostramos los diagramas de
Warnier correspondientes.
5.2 La lista de registros 220

Código 5.17 Métodos que entregan el contenido de un registro determinado CursoListas (1/2)

10500 /∗ ∗
10600 ∗ Método <code>daNombre</code> r e g r e s a e l nombre e d i t a d o con
10700 ∗ l o s b l a n c o s r e q u e r i d o s p a r a a l c a n z a r e l tamaño e s p e c i f i c a d o .
10800 ∗ @param e s t u d i a n t e de t i p o <code>E s t u d i a n t e </code >, l a
10900 ∗ r e f e r e n c i a a l e s t u d i a n t e deseado .
11000 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, l a c a d e n a con e l
11100 ∗ nombre e d i t a d o .
11200 ∗/
11300 p u b l i c S t r i n g daNombre ( E s t u d i a n t e e s t u d i a n t e ) {
11400 i f ( e s t u d i a n t e != n u l l ) {
11500 r e t u r n e s t u d i a n t e . daNombre ( ) ;
11600 }
11700 r e t u r n " Estudiante invalido " ;
11800 }
11900
12000 /∗ ∗
12100 ∗ Método <code>d a C a r r e r a </code> r e g r e s a l a c a r r e r a e d i t a d a con e l
12200 ∗ nombre de l a misma y l o s b l a n c o s r e q u e r i d o s p a r a a l c a n z a r e l
12300 ∗ tamaño e s p e c i f i c a d o .
12400 ∗ @param e s t u d i a n t e de t i p o <code>E s t u d i a n t e </code >, l a
12500 ∗ r e f e r e n c i a a l e s t u d i a n t e deseado .
12600 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, l a c a d e n a con l a
12700 ∗ carrera editada .
12800 ∗/
12900 public String daCarrera ( Estudiante estudiante ) {
13000 i f ( e s t u d i a n t e != n u l l ) {
13100 return e s t u d i a n t e . daCarrera ( ) ;
13200 }
13300 r e t u r n " Estudiante no valido " ;
13400 }
13500
13600 /∗ ∗
13700 ∗ Método <code>d a C o r r e o </code> r e g r e s a e l c o r r e o e d i t a d o con l o s
13800 ∗ b l a n c o s r e q u e r i d o s p a r a a l c a n z a r e l tamaño e s p e c i f i c a d o .
13900 ∗ @param e s t u d i a n t e de t i p o <code>E s t u d i a n t e </code >, l a
14000 ∗ r e f e r e n c i a a l e s t u d i a n t e deseado .
14100 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, l a c a d e n a con e l
14200 ∗ correo editado .
14300 ∗/
14400 public S t r i n g daCorreo ( Estudiante e s t u d i a n t e ) {
14500 i f ( e s t u d i a n t e != n u l l ) {
14600 return e s t u d i a n t e . daCorreo ( ) ;
14700 }
14800 r e t u r n " Estudiante no valido " ;
14900 }
221 Datos estructurados

Código 5.17 Métodos que entregan el contenido de un registro determinado CursoListas (2/2)

15100 /∗ ∗
15200 ∗ Método <code>daCuenta </code> r e g r e s a e l nombre e d i t a d o con
15300 ∗ l o s c e r o s r e q u e r i d o s p a r a a l c a n z a r e l tamaño e s p e c i f i c a d o .
15400 ∗ @param e s t u d i a n t e de t i p o <code>E s t u d i a n t e </code >, l a
15500 ∗ r e f e r e n c i a a l e s t u d i a n t e deseado .
15600 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, l a c a d e n a con e l
15700 ∗ numero de c u e n t a e d i t a d o .
15800 ∗/
15900 p u b l i c S t r i n g daCuenta ( E s t u d i a n t e e s t u d i a n t e ) {
16000 i f ( e s t u d i a n t e != n u l l ) {
16100 r e t u r n e s t u d i a n t e . daCuenta ( ) ;
16200 }
16300 r e t u r n " Estudiante no valido " ;
16400 }
16500
16600 /∗ ∗
16700 ∗ Método <code>a r m a R e g i s t r o </code >, arma un r e n g l ó n p a r a
16800 ∗ l a t a b l a que s e va a m o s t r a r en un l i s t a d o .
16900 ∗ @param e s t u d i a n t e t i p o <code>E s t u d i a n t e </code >, e l
17000 ∗ r e g i s t r o a armar .
17100 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, una c a d e n a con e l
17200 ∗ r e g i s t r o formateado y editado .
17300 ∗/
17400 public String armaRegistro ( Estudiante estudiante ) {
17500 i f ( e s t u d i a n t e != n u l l ) {
17600 return e s t u d i a n t e . armaRegistro ( ) ;
17700 }
17800 return n u l l ;
17900 }

Podemos empezar ya con las funciones propias de la base de datos, como agre-
gar un estudiante a la lista. Realmente podrı́amos agregarlo de tal manera que se
mantenga un cierto orden alfabético o simplemente agregarlo en cualquier posición
de la misma. Haremos esto último; el mantener la lista ordenada se hará poste-
riormente.
Hay dos posiciones “lógicas” para agregar un registro. La primera de ellas, la
más sencilla, es al inicio de la lista. Básicamente lo que tenemos que hacer es poner
al registro que se desea agregar como el primer registro de la lista. Esto quiere
decir que la nueva lista consiste de este primer registro, seguido de la vieja lista.
Veamos en la figura 5.13 en la siguiente página qué es lo que queremos decir.
El diagrama de Warnier para hacer esto está en la figura 5.14 en la siguiente
página. Hay que considerar el caso en que estamos ingresando al primer elemento
de la lista, en cuyo caso se trata también del último en la lista.
5.2 La lista de registros 222

Figura 5.13 Esquema del agregado de un registro al principio de la lista

Antes:
@ lista @ ultimo

Inf o @ Inf o @ Inf o @ Inf o @


[]

@ Inf o @
[]
nuevo

Después:
@ lista @ ultimo

Inf o @ Inf o @ Inf o @ Inf o @


[]

@ Inf o @
nuevo

Figura 5.14 Agregando al principio de la lista


$ !
'
'
' Lista vacı́a Poner a último a apuntar a nuevo
'
'
' À
'
'
'
'
'
'
'
' !
'
&Lista vacı́a ∅
Agrega registro
al principio '
'
'
'
'
'
Poner a nuevo a apuntar al
'
'
' primer elemento de la lista
'
'
'
'
'
'
%Poneralanuevo
lista a apuntar
elemento
La programación de este método se encuentra en el listado 5.18.
223 Datos estructurados

Código 5.18 Agregar un registro al principio de la lista CursoListas

18100 /∗ ∗
18200 ∗ Método <code>agregaAlumno </code >, i n s e r t a a l r e g i s t r o r e f e r i d o
18300 ∗ p o r nuevo en l a l i s t a de r e g i s t r o s , en e l l u g a r que l e
18400 ∗ c o r r e s p o n d e a l f a b é t i c a m e n t e .
18500 ∗ @param nuevo de t i p o <code>E s t u d i a n t e </code >, e l r e g i s t r o que
18600 ∗ se desea i n s e r t a r .
18700 ∗/
18800 p u b l i c v o i d agregaAlumno ( E s t u d i a n t e nuevo ) {
18900 i f ( l i s t a == n u l l ) { // e l p r i m e r o e s t a m b ié n e l ú l t i m o
19000 u l t i m o = nuevo ;
19100 }
19200 nuevo . s e t S i g u i e n t e ( l i s t a ) ; // A g r e g a r l o a l p r i n c i p i o
19300 l i s t a = nuevo ; // l a l i s t a a h o r a e m p i e z a en e l nuevo
19400 numRegs ++;
19500 }

Es importante el orden en que se redirigen las referencias. Si primero redi-


rigimos a la cabeza de la lista perdemos la lista, por lo que primero hay que
“amarrar” el inicio anterior de la lista registrándolo en la referencia que tiene
nuevo al siguiente de la lista.
Podemos tener otra firma de este mismo método, que es cuando el usuario pro-
porciona directamente los campos del registro. En ese caso simplemente armamos
al vuelo el objeto de la clase Estudiante e invocamos al método que acabamos de
dar. El código se puede ver en el listado 5.19.

Código 5.19 Agregar un estudiante con especificación de cada campo CursoListas

197 /∗ ∗
198 ∗ Método <code>agregaAlumno </code> l o que h a c e .
199 ∗ @param nmbre de t i p o <code>S t r i n g </code> .
200 ∗ @param c t a de t i p o <code>i n t </code> .
201 ∗ @param c a r r e r a de t i p o <code>i n t </code> .
202 ∗ @param c o r r e o de t i p o <code>S t r i n g </code> .
203 ∗/
204 p u b l i c v o i d agregaAlumno ( S t r i n g nmbre , i n t c a r r e r a , S t r i n g cta ,
205 String correo ) {
206 agregaAlumno ( new E s t u d i a n t e ( nmbre , c o r r e o , c t a , c a r r e r a ) ) ;
207 }

Si quisiéramos acomodar al registro nuevo al final de la lista y tuviéramos que


recorrer la lista para encontrar al último registro en ella las cosas se complicarı́an
5.2 La lista de registros 224

un poco más, aunque no demasiado. Queremos hacer lo que aparece en la figu-


ra 5.15 con la lı́nea punteada. Afortunadamente tuvimos precaución de mantener
una referencia al último elemento de la lista.

Figura 5.15 Esquema para agregar al final de la lista


@ lista

Inf o @ Inf o @ Inf o @ Inf o @


[]
@

ultimo

@ @
Inf o [ ]
nuevo

Debemos tomar la precaución que si la lista está vacı́a cuando vamos a ingresar
el registro, éste es el primero de la lista pero también el último. El algoritmo se
muestra en el diagrama de Warnier de la figura 5.16.

Figura 5.16 Algoritmo para agregar al final de la lista


$ #
'
'
' lista apunta a nuevo
'
'
'
lista vacı́a
'
'
'
ultimo apunta a nuevo
'
' À
'
'
& $
'
'
'
Agregar registro
' &Poner al registro apuntado por
al final de la lista '
'
'
'
'
lista vacı́a
' ultimo a apuntar a nuevo
'
' '
'
% Poner a ultimo a apuntar
'
'
'
'
'
%
a nuevo

La programación del algoritmo queda como se ve en el listado 5.20 en la página


opuesta. También acá el orden en que se actualizan las referencias es importante,
ya que si primero modificamos la referencia ultimo ya no tendrı́amos un acceso
directo al último registro actual de la lista.
225 Datos estructurados

Código 5.20 Agregar un registro al final de la lista CursoListas

20900 /∗ ∗
21000 ∗ Método <code>a g r e g a A l F i n a l </code> a g r e g a a un r e g i s t r o a l f i n a l
21100 ∗ de l a l i s t a .
21200 ∗ @param nuevo de t i p o <code>E s t u d i a n t e </code >, e l r e g i s t r o a
21300 ∗ agregar t a l cual .
21400 ∗/
21500 p u b l i c v o i d a g r e g a A l F i n a l ( E s t u d i a n t e nuevo ) {
21600 i f ( l i s t a == n u l l ) {
21700 l i s t a = nuevo ;
21800 u l t i m o = nuevo ;
21900 } else {
22000 u l t i m o . s e t S i g u i e n t e ( nuevo ) ;
22100 u l t i m o = nuevo ;
22200 }
22300 numRegs++;
22400 }

También en este método conviene tener otra firma para cuando el usuario
proporciona directamente los campos del registro. Al igual que en el caso anterior
simplemente armamos al vuelo el objeto de la clase Estudiante e invocamos al
método que acabamos de dar. El código se puede ver en el listado 5.21.

Código 5.21 Método que recibe los campos para agregar a un estudiante CursoListas

22600 /∗ ∗
22700 ∗ Método <code>a g r e g a A l F i n a l </code> c o n s t r u y e un r e g i s t r o y l o
22800 ∗ a g r e g a a l f i n a l de l a l i s t a .
22900 ∗ @param nmbre t i p o <code>S t r i n g </code >, e l nombre d e l
23000 ∗ estudiante .
23100 ∗ @param c a r r e r a t i p o <code>i n t </code >, l a c l a v e de l a c a r r e r a .
23200 ∗ @param c t a t i p o <code>S t r i n g </code >, e l numero de c u e n t a d e l
23300 ∗ estudiante .
23400 ∗ @param c o r r e o t i p o <code>S t r i n g </code >, e l c o r r e o d e l
23500 ∗ estudiante .
23600 ∗/
23700 p u b l i c v o i d a g r e g a A l F i n a l ( S t r i n g nmbre , i n t c a r r e r a , S t r i n g cta ,
23800 String correo ) {
23900 a g r e g a A l F i n a l ( new E s t u d i a n t e ( nmbre , c o r r e o , c t a , c a r r e r a ) ) ;
24000 }

Sigamos implementando aquellas funciones que responden al patrón de proceso


que dimos para recorrer una lista. Por ejemplo, el método que lista todos los
registros responde exactamente a este patrón. Para facilitar la interacción con el
usuario, el método construye una cadena con los registros editados y es lo que
entrega. El algoritmo lo podemos ver en la figura 5.17 en la siguiente página. Lo
5.2 La lista de registros 226

primero que hacemos al entrar a un método de este tipo es verificar si la lista


está vacı́a. Si no lo está procederemos a recorrerla.

Figura 5.17 Imprimiendo todos los registros


$ $ #
'
'
' '
'
' Reportar error
'
'
' '
'
'
lista vacı́a
'
'
' & Salir con ”No hay”
'
'
' Inicialización
'
` #
'
'
' '
'
'
'
' '
'
'
Colocarse al principio de la lista
'
'
& % lista vacı́a
Acumulador de cadena Ð ””
Imprime lista
' $
'
'
' '
'
'
' Procesa el registro &Agrega el registro actual
'
'
' actual
'
'
' haya) ' ' editado a cadena
'
'
(mientras %
'
'
'
Pasa al registro siguiente
'
'
' !
%Final Entrega la cadena

La programación se encuentra en el listado 5.22.


227 Datos estructurados

Código 5.22 Imprimiendo toda la lista CursoListas

24200 /∗ ∗
24300 ∗ Método <code>d a m e L i s t a </code> r e g r e s a una c a d e n a que
24400 ∗ r e p r e s e n t a a una t a b l a con l o s d i s t i n t o s r e g i s t r o s .
24500 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, que e s l a t a b l a con
24600 ∗ cada r e g i s t r o en un r e n g l ó n y a l i n e a d o .
24700 ∗/
24800 public S t r i n g dameLista () {
24900 i f ( l i s t a == n u l l ) { // V e r i f i c a r l i s t a v a cı́ a
25000 r e t u r n "No hay estudiantes registrados en el grupo " ;
25100 }
25200 // I n i c i a l i z a c i ó n
25300 S t r i n g s L i s t a = "" ; // P r e p a r a a c u m u l a d o r e s
25400 E s t u d i a n t e a c t u a l = l i s t a ; // C o l o c a r s e p r i n c i p i o de l i s t a
25500 w h i l e ( a c t u a l != n u l l ) { // R e p e t i r m i e n t r a s haya en l a l i s t a
25600 s L i s t a = s L i s t a + "\n" // P r o c e s a r r e g i s t r o
25700 + actual . armaRegistro ( ) ;
25800 a c t u a l = a c t u a l . g e t S i g u i e n t e ( ) ; // P a s a r a l s i g u i e n t e
25900 }
26000 return s L i s t a ; // R e g r e s a r v a l o r b u s c a d o
26100 }

Una vez que tenemos el método que nos construye la lista del grupo podemos
pedir el “acta”, que serı́a un listado con el número de grupo y el número de
estudiantes inscritos. Lo único que tenemos que hacer es agregar estos dos últimos
datos a la lista que nos entrega el método dameLista. El código se puede ver en el
listado 5.23.

Código 5.23 Método que da el acta del grupo CursoListas


263 /∗ ∗
264 ∗ Método <code>dameCurso </code> e n t r e g a g r u p o b i e n e d i t a d o
265 ∗ en forma de t a b l a , p r e c e d i d o p o r e l numero d e l g r u p o .
266 ∗ @ r e t u r n t i p o a <code>S t r i n g </code> l a t a b l a c o m p l e t a .
267 ∗/
268 p u b l i c S t r i n g dameCurso ( ) {
269 r e t u r n " Grupo : " + Cadenas . r e l l e n a C a m p o ( grupo , TAM GPO, ’0’ , ’i’ )
270 + "\tNum. de alumnos : " + numRegs
271 + "\n\n" + d a m e L i s t a ( ) ;
272 }

Para el método que escribe todos los registros que cazan con una cierta subca-
dena también vamos a usar este patrón, pues queremos revisar a todos los registros
y, conforme los vamos revisando, decidir para cada uno de ellos si se elige (impri-
5.2 La lista de registros 228

me) o no. El algoritmo se encuentra en la figura 5.18 –obviamos en el algoritmo


la verificación de si la lista está vacı́a–.

Figura 5.18 Impresión de registros seleccionados


$ !
'
'
' Inicialización Toma el primero de la lista
'
'
' $ $
'
'
' ' &
'
'
' '
'
'
El campo en el registro Acepta el
'
'
' '
'
'
actual caza con la sub-
% registro
'
'
' '
'
'
cadena
' ' À
Construye lista '
& Procesa el '
& $
El campo en el registro '
de registros registro actual
seleccionados ''
' '
'
' &
'
'
' (mientras haya) '
'
' actual NO caza con la
'
'
' '
'
' subcadena '
% ∅
'
'
' '
'
'
'
'
' %Toma el registro siguiente
'
'
'
'
' !
%Final Entrega lista construida

El único punto fino es cómo se decide si un campo cumple o no. Para ello
lo que hacemos es ver si el campo solicitado contiene a la subcadena indicada o
no, cuando no se trata del campo de la carrera, que es un entero. En el caso del
campo de la carrera, que en la base de datos es un entero pero en el argumento
es una cadena, debemos verificar si es una clave de carrera válida o asignar un 0
si no lo es –lı́neas 30600 a 31000–: Procedemos a convertir la cadena en el entero
correspondiente –lı́nea 30700–, verificamos que sea una clave válida de carrera (o
0) –lı́neas 30800 a 31000– y procedemos a verificar con la anotada en el registro
actual –lı́nea 31100–. Estamos suponiendo que nos están pasando la clave de l
carrera en una variable de cadena.
229 Datos estructurados

La programación de este método se puede ver en el listado 5.24.

Código 5.24 Impresión de registros seleccionados CursoListas (1/2)

27500 /∗ ∗
27600 ∗ Método <code>losQueCazanCon </code >: arma una l i s t a con l o s
27700 ∗ r e g i s t r o s que cumplen t e n e r una s u b c a d e n a .
27800 ∗ @param cad de t i p o <code>S t r i n g </code >, l a c a d e n a a b u s c a r .
27900 ∗ @param campo de t i p o <code>i n t </code >, e l campo d e n t r o d e l
28000 ∗ r e g i s t r o a buscar .
28100 ∗ @ r e t u r n t i p o a <code>S t r i n g </code >, una l i s t a con
28200 ∗ t o d o s l o s r e g i s t r o s que cumplen .
28300 ∗/
28400 p u b l i c S t r i n g losQueCazanCon ( S t r i n g cad , i n t campo ) {
28500 // V e r i f i c a r que l o s a r g u m e n t o s s o n c o r r e c t o s
28600 i f ( cad == n u l l ) {
28700 r e t u r n " Subcadena inválida para buscar " ;
28800 }
28900 i f ( campo < NOMBRE | | campo > CUENTA) {
29000 r e t u r n " Campo inválido " ;
29100 }
29200 // D e c l a r a c i o n e s e i n i c i a l i z a c i ó n
29300 S t r i n g s L i s t a = "" ; // Para a c u m u l a r l o s r e g i s t r o s
29400 Estudiante actual = l i s t a ; // Para r e c o r r e r l a l i s t a y c o l o 
29500 // c a r s e a l p r i n c i p i o de l a l i s t a
29600 i n t l u g a r =  1; // Para v e r i f i c a r s i un r e g i s t r o cumple
29700 // R e c o r r e r l i s t a s ( m i e n t r a s haya )
29800 w h i l e ( a c t u a l != n u l l ) {
29900 l u g a r =  1; // Suponemos que no cumple
30000 // V e r i f i c a r s i cumple l a c o n d i c i ó n
30100 i f ( campo != CARRERA) { // C o n v e r t i r a m i n ú s c u l a s cad b u s c a d a
30200 cad = cad . t o L o w e r C a s e ( ) . t r i m ( ) ;
30300 l u g a r = a c t u a l . daCampo ( campo ) . t o L o w e r C a s e ( ) . t r i m ( )
30400 . i n d e x O f ( cad ) ;
30500 } // t h e n
30600 e l s e { // Suponemos que e s l a c l a v e de l a c a r r e r a
30700 l u g a r = I n t e g e r . p a r s e I n t ( cad ) ;
30800 i f (! CatalogoCarreras . esCarrera ( lugar )) {
5.2 La lista de registros 230

Código 5.24 Imprimiendo registros seleccionados CursoListas (2/2)

30900 lugar = 0;
31000 }
31100 i f ( l u g a r != a c t u a l . g e t C a r r e r a ( ) ) {
31200 l u g a r =  1;
31300 } // l u g a r queda con 1 s i no s o n i g u a l e s
31400 } // e l s e
31500 i f ( l u g a r != 1) {
31600 s L i s t a = s L i s t a + a c t u a l . a r m a R e g i s t r o ( ) +"\n" ;
31700 }
31800 // P a s a r a l s i g u i e n t e
31900 actual = actual . getSiguiente ();
32000 } // t e r m i n a e l w h i l e
32100 return s L i s t a ;
32200 }

En el listado 5.24 hacemos referencia al método daCampo que implementamos


ya en el listado 5.6 en la página 198. En su momento explicamos las caracterı́sticas
del mismo, por lo que ya no hay necesidad de repetirlas.
Para los métodos localizaAlumno y eliminaAlumno el patrón que debemos seguir
al recorrer la lista es un poco distinto, porque tenemos que parar en el momento
en que encontremos lo que estamos buscando y ya no seguir recorriendo la lista.
Pero deberemos prevenir el que lo que buscamos no se encuentre en la lista. El
patrón general para este tipo de búsquedas se muestra en la figura 5.19.

Figura 5.19 Patrón de búsqueda de un registro que cumpla con . . .


$ $
'
' '
'
'
'
'
' '
&
Inicialización de los
'
'
'
“acumuladores”
'
'
'
Inicialización
'
'
' '
'
'
Colocarse al principio
'
'
' % de la lista
'
'
'
'
' $ $
& '
' &Procesa el registro
Busca algo '
'
'
en una lista '
' '
& Es el solicitado
%Regresar esta
'
'
'
Revisa registros
referencia
'
'
'
(mientras haya y
' À
'
' no lo encuentres) '
'
'
'
' '
' !
'
'
' % Es el solicitado Toma el siguiente
'
'
'
'
'
' !
%No encontró el registro Emite mensaje de error

Para el caso en que se busca un registro que tenga una subcadena en un campo
231 Datos estructurados

determinado, el proceso del registro que no caza no es ninguno, mientras que el


proceso del que caza es simplemente regresar la referencia del registro que cazó y
salir del método. La programación de este método queda como se muestra en
el listado 5.25 en la siguiente página. Como se puede observar, la condición de
parada únicamente verifica que no se haya acabado la lista –lı́nea 34400–, ya que
al encontrar al registro buscado se saldrá de la iteración (y del método).

El algoritmo que acabamos de dar es general. Queremos que empiece a buscar


a partir de un registro determinado. Esto es necesario porque varios registros
pueden compartir un mismo dato y no queremos forzosamente el primero que
aparezca. Por lo tanto, en lugar de colocarnos al principio de la lista, le hemos
dado al método la referencia desde la que queremos que busque, desde. El método
se inicializa colocándose frente al registro solicitado y busca a partir de él. Podemos
tener un método que empiece siempre al principio de la lista; lo único que tenemos
que hacer es a este mismo método pasarle el principio de la lista como punto inicial.
Este último método queda como se muestra en el listado 5.25.
5.2 La lista de registros 232

Código 5.25 Método que busca un registro CursoListas (1/2)

32400 /∗ ∗
32500 ∗ Método <code>l o c a l i z a A l u m n o </code> l o que h a c e .
32600 ∗ @param q u i e n de t i p o <code>S t r i n g </code >, l a c a d e n a que s e e s t á
32700 ∗ buscando .
32800 ∗ @param campo de t i p o <code>i n t </code >, e l campo donde s e va a
32900 ∗ buscar .
33000 ∗ @param d e s d e de t i p o <code>E s t u d i a n t e </code >, e l r e g i s t r o a
33100 ∗ p a r t i r d e l c u a l empieza l a s u b l i s t a .
33200 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >, l a r e f e r e n c i a
33300 ∗ d e l r e g i s t r o que cumple l a c o n d i c i ó n .
33400 ∗/
33500 p u b l i c E s t u d i a n t e l o c a l i z a A l u m n o ( S t r i n g q u i e n , i n t campo ,
33600 Estudiante desde ) {
33700 i f ( q u i e n == n u l l ) {
33800 return n u l l ;
33900 }
34000 quien = quien . trim ( ) . toLowerCase ( ) ;
34100 // r e c o r r e l i s t a
34200 Estudiante a c t u a l = desde ;
34300 i n t l u g a r =  1; // Para d e t e c t a r l o c a l i z a c i ó n en c a d e n a s
34400 w h i l e ( a c t u a l != n u l l ) {
34500 l u g a r =  1; // Suponemos que no cumple
34600 // V e r i f i c a r s i cumple l a c o n d i c i ó n
34700 i f ( campo != CARRERA) {
34800 l u g a r = a c t u a l . daCampo ( campo ) . t r i m ( ) . t o L o w e r C a s e ( )
34900 . indexOf ( quien ) ;
233 Datos estructurados

Código 5.25 Método que busca un registro CursoListas (2/2)

35000 } e l s e { // Suponemos que e s l a c l a v e de l a c a r r e r a


35100 lugar = Integer . parseInt ( quien ) ;
35200 i f (! CatalogoCarreras . esCarrera ( lugar )) {
35300 lugar = 0;
35400 }
35500 i f ( l u g a r != a c t u a l . g e t C a r r e r a ( ) ) {
35600 l u g a r =  1;
35700 } // l u g a r queda con 1 s i no s o n i g u a l e s
35800 } // S i s e cumple l a c o n d i c i ó n
35900
36000 i f ( l u g a r != 1) {
36100 return a c t u a l ;
36200 }
36300 actual = actual . getSiguiente ();
36400 } // w h i l e
36500 // S i l l e g a m o s acá e s p o r q u e no s e e n c o n t r ó
36600 System . o u t . p r i n t l n ( " Registro no encontrado " ) ;
36700 return n u l l ;
36800 }

Si ya tenemos este método es fácil programar el que hace la búsqueda desde el


principio de la lista, pasándole únicamente lo que se busca e invocando al método
que acabamos de dar con la cabeza de la lista como primer registro. Este método
se encuentra en el listado 5.26.

Código 5.26 Buscando desde el principio de la lista CursoListas

37000 /∗ ∗
37100 ∗ Método <code>l o c a l i z a A l u m n o </code> l o c a l i z a a un
37200 ∗ <code>E s t u d i a n t e </code> a p a r t i r d e l i n i c i o de l a l i s t a .
37300 ∗ @param q u i e n de t i p o <code>S t r i n g </code >, l a s u b c a d e n a que s e
37400 ∗ busca .
37500 ∗ @param campo t i p o <code>i n t </code >, c l a v e d e l campo
37600 ∗ donde s e b u s c a .
37700 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >, l a r e f e r e n c i a a l
37800 ∗ r e g i s t r o que c o n t i e n e a l a s u b c a d e n a en e l campo e s p e c i f i c a d o .
37900 ∗/
38000 p u b l i c E s t u d i a n t e l o c a l i z a A l u m n o ( S t r i n g q u i e n , i n t campo ) {
38100 // r e c o r r e l i s t a a p a r t i r d e l p r i m e r o
38200 r e t u r n l o c a l i z a A l u m n o ( q u i e n , campo , l i s t a ) ;
38300 }
5.2 La lista de registros 234

5.2.1. Paso por valor y por referencia

Es el momento adecuado para insistir sobre lo que pasa cuando dentro de un


método o función se altera el valor de un parámetro, como sucede en la lı́nea 34000
en el listado 5.25 en la página 232. En Java se dice que los parámetros pasan por
valor. Esto quiere decir que al llamar a un método o función se hace una copia
del valor de los parámetros y esta copia es la que se entrega al método. Si el
parámetro es un valor primitivo, se pasa una copia de ese valor; si el parámetro
es un objeto (una variable de referencia) se pasa una copia de la referencia –la
dirección en el heap–, que es, precisamente, el valor de la variable. Por lo tanto,
todo lo que el método haga con ese parámetro no se reflejará fuera del método,
ya que trabaja con una copia. Cuando un parámetro se pasa por valor se dice que
es un parámetro de entrada, que se evalúa lo antes posible, en el primer momento
en que aparece: en la llamada.
A lo mejor esto resulta un poco confuso cuando pasamos objetos como paráme-
tros. Cuando se pasa un objeto como parámetro se pasa la copia de una variable,
que es una variable de referencia, contiene la dirección donde se encuentra el ob-
jeto en el heap. Como vamos a trabajar directamente con el objeto en el heap, ya
que tenemos su referencia, las modificaciones que se hagan dentro del método al
objeto referido sı́ se van a reflejar fuera del método, ya que copia o no, el argumen-
to contiene una dirección válida del heap. Los cambios a los que nos referimos son
aquéllos que se refieren al valor de la referencia, o sea, ponerlos a apuntar a otro
lado. Por ejemplo, si dentro del método construimos un objeto nuevo y ponemos
al parámetro a apuntar a él, como se muestra en el código en el listado 5.27.

Código 5.27 Paso por valor de una variable de objeto Estudiante (1/2)

100 /∗ ∗
200 ∗ Método <code>c o p i a </code >: c o n s t r u y e una c o p i a y d e j a a nuevo
300 ∗ ap un ta ndo a l a c o p i a .
400 ∗ @param v i e j o t i p o <code>E s t u d i a n t e </code >: e l o b j e t o a c o p i a r .
500 ∗ @param nuevo t i p o <code>E s t u d i a n t e </code >: e l o b j e t o c o p i a d o .
600 ∗/
700 p u b l i c v o i d c o p i a ( E s t u d i a n t e v i e j o , E s t u d i a n t e nuevo ) {
800 nuevo = new E s t u d i a n t e ( ) ;
900 nuevo . s e t C a r r e r a ( v i e j o . g e t C a r r e r a ( ) ) ;
1000 nuevo . setNombre ( v i e j o . getNombre ( ) ) ;
1100 nuevo . s e t C o r r e o ( v i e j o . g e t C o r r e o ( ) ) ;
1200 nuevo . s e t C u e n t a ( v i e j o . g e t C u e n t a ( ) ) ;
1300 v i e j o . s e t C u e n t a ( " 444444444 " ) ;
1400 }
...
235 Datos estructurados

Código 5.27 Paso por valor de una variable de objeto Estudiante (2/2)

10000 /∗ Supongamos l a l l a m a d a d e s d e main de l a s i g u i e n t e forma : ∗/


10100 p u b l i c s t a t i c v o i d main { S t r i n g [ ] a r g s ) {
10200 E s t u d i a n t e v i e j o = new E s t u d i a n t e ( "Lola" ,
10300 " lolita@ciencias " , " 99999999 " , 1 2 7 ) ;
10400 E s t u d i a n t e nuevo = new E s t u d i a n t e ( ) ;
10500 c o p i a ( v i e j o , nuevo ) ;
...

Supongamos que se ejecutan las lı́neas 10200 a 10400 y la llamada se realiza


como se muestra en la lı́nea 10500. Antes de entrar al método tenemos dos variables
de objeto (referencias) y la memoria del programa se encuentra como se muestra
en la figura 5.20.

Figura 5.20 Paso por valor de una variable de objeto

Variables Heap

nombre cuenta carrera correo signte

(4086)
(40800) (32100)
127 (3198)
[]
(4086)
viejo (3198)
“lolita@ciencias”
(32100) “99999999”
(40800) “Lola”

nombre cuenta carrera correo signte

(75300)
(8200) (5470)
0 (2500)
[]
nuevo (75300)
(2500)
“”
(5470) “”
(8200) “”

Una vez dentro del método, después de ejecutarlo pero antes de salir del mismo
–lı́nea 1300–, la memoria del programa se ve como se muestra en la figura 5.21.
5.2 La lista de registros 236

Figura 5.21 Paso por valor de una variable de objeto, dentro del método

Variables Heap
nombre cuenta carrera correo signte

(35710)
(6020) (5432)
122 (8910)
[]
nuevo (35710)
(8910)
“lolita@ciencias”
(copia)
(5432) “99999999”
(6020) “Lola”
(4086)

viejo
(copia)
nombre cuenta carrera correo signte

(4086)
(40800) (88888)
122 (3198)
[]
(4086)
viejo (3198)
“lolita@ciencias”
(main)
(88888) “444444444”
(40800) “Lola”

nombre cuenta carrera correo signte

(75300)
(8200) (5470)
0 (2500)
[]
nuevo (75300)
(2500)
“”
(main)
(5470) “”
(8200) “”

Debemos notar que tenemos espacio nuevo para un objeto, cuya referencia
se encuentra en la variable local nuevo; como copiamos la referencia a viejo, el
cambio que se hace en la lı́nea 1300 del campo cuenta del objeto cuya referencia
se encuentra en viejo, se hace directamente sobre el objeto y el cambio permanece
más allá de la ejecución del método.
Al salir del método desaparecen las variables locales –las copias de los ar-
gumentos incluidas– aunque los objetos construidos en el heap permanecen. Por
ejemplo, el registro creado en nuevo permanece, aunque ya no tiene ninguna refe-
rencia viva, por lo que se convierte en basura –no hay manera de acceder a él– y
las variables de objeto mantienen el valor que tenı́an antes de entrar al método.
237 Datos estructurados

Figura 5.22 Paso por valor de una variable de objeto, después de salir del método

Variables Heap
nombre cuenta carrera correo signte

(35710)
(6020) (5432)
122 (8910)
[]
nuevo (35710)
(8910)
“lolita@ciencias”
(5432) “99999999”
(4086)

viejo (6020) “Lola”

nombre cuenta carrera correo signte

(4086)
(40800) (32100)
122 (3198)
[]
(4086)
viejo (3198)
“lolita@ciencias”
(32100) “44444444”
(40800) “Lola”
nombre cuenta carrera correo signte

(75300)
(8200) (5470)
0 (2500)
[]
nuevo (75300)
(2500)
“”
(5470) “”
(8200) “”

Para resumir, los cambios hechos directamente a variables pasadas como pará-
metros dentro de los métodos (sean referencias o valores) no se reflejan al salir del
método; sin embargo, si se usa el valor de una variable de referencia (la dirección
del objeto) el objeto sı́ puede ser modificado y las modificaciones permanecerán
después de que termine la ejecución del método. Por ejemplo, en la lı́nea 34000
del listado 5.25 en la página 232, los cambios a quien se van a ver sólo mientras
permanezcamos en buscaAlumno. Una vez que salgamos, podremos observar que
la cadena que se utilizó como parámetro no sufrió ningún cambio.
Otro mecanismo común de pasar parámetros, presente en algunos lenguajes de
programación, es lo que se conoce como paso por referencia. Con este mecanismo lo
que se pasa al método es la dirección (referencia) donde se encuentra la variable
en cuestión –ya sea que se trate de variables primitivas o de objetos–. Si este
mecanismo estuviese presente en Java, lo que estarı́amos pasando, tratándose de
objetos, serı́a la dirección en memoria donde se encuentra la variable que contiene
a la referencia. Java no tiene este mecanismo de pasar parámetros, pero se lo
pueden encontrar en lenguajes como C, C++ y Pascal. Cuando un parámetro se
pasa por referencia, generalmente se hace para dejar ahı́ algún valor, por lo que
5.2 La lista de registros 238

se dice que es un parámetro de salida, o de entrada y salida.


En los lenguajes mencionados se usan parámetros por referencia para que los
métodos puedan devolver más de un valor. En Java esto se harı́a construyendo
un objeto que tuviera a los campos que queremos regresar, regresando al objeto
como resultado del método.
Supongamos que queremos regresar los valores máximo y mı́nimo de las ca-
rreras en una lista del curso que tenemos. Tendrı́amos que declarar un objeto con
dos campos enteros –max y min– para dejar allı́ los resultados, como se ve en el
listado 5.28 –no es necesario comentar lo que hace la clase–.

Código 5.28 Clase para regresar dos valores de un método DosValores

public class DosValores {


p r i v a t e i n t max ,
min ;
p u b l i c i n t getMin ( ) {
r e t u r n min ;
}
p u b l i c i n t getMax ( ) {
r e t u r n max ;
}
p u b l i c v o i d s e t M i n ( i n t min ) {
t h i s . min = min ;
}
p u b l i c v o i d setMax ( i n t max ) {
t h i s . max = max ;
}
}

En la clase CursoLista podrı́amos tener un método que recorriera el grupo y


fuera registrando los valores máximo y mı́nimo, como se muestra en el listado 5.29,
donde también ponemos lo correspondiente a la llamada desde el método main.

Código 5.29 Regreso de dos valores primitivos CursoLista (1/2)

public DosValores maxyMinCarreras ( ) {


i f ( l i s t a == n u l l ) {
System . o u t . p r i n t l n ( "No hay nadie en la lista " ) ;
return n u l l ;
}
i n t max , min ;
max = min = l i s t a . g e t C a r r e r a ( ) ; // V a l o r e s p o r o m i s i ó n
E s t u d i a n t e a c t u a l = l i s t a . g e t S i g u i e n t e ( ) ; // Empezar d e l 2 do
239 Datos estructurados

Código 5.30 Regreso de dos valores primitivos CursoLista (2/2)

w h i l e ( a c t u a l != n u l l ) {
int laCarre = actual . getCarrera ();
i f ( l a C a r r e > max )
max = l a C a r r e ;
i f ( l a C a r r e < min )
min = l a C a r r e ;
actual = actual . getSiguiente ();
} // w h i l e
// Se acabó l a l i s t a
D o s V a l o r e s r e g r e s o = new D o s V a l o r e s ( ) ;
r e g r e s o . setMax ( max ) ;
r e g r e s o . s e t M i n ( min ) ;
return r e g r e s o ;
}
...

System . o u t . p r i n t l n ( " Máximo de carrera " ) ;


D o s V a l o r e s v a l o r e s = enOrden . m a x y M i n C a r r e r a s ( ) ;
System . o u t . p r i n t l n ( "max=" + v a l o r e s . getMax ( )
+ "\tmin=" + v a l o r e s . getMin ( ) ) ;

El método regresa un objeto de la clase DosValores2 , que tiene métodos para


extraer los valores regresados. Se declara un objeto de la clase referida y se invoca
al método que nos “arma” el objeto con los valores deseados. El algoritmo para
seleccionar los valores es un ejemplar del algoritmo que recorre la lista. Para el
proceso de cada registro hacemos lo que se muestra en el diagrama de la figura 5.23.

Figura 5.23 Obtención del máximo y mı́nimo en una lista


$ #
'
'
' max Ð primer valor
'
' Inicio
'
'
' min Ð primer valor
'
'
' $
'
'
& '
'
' valTemp Ð actual.getCarrera()
!
Obtener máximo '
&
y mı́nimo '
' Procesar registro max <valTemp max Ð valTemp
'
'
' (mientras haya) '
'
' '
'
' !
'
'
' %min >valTemp min Ð valTemp
'
'
'
% Regresar objeto con min y max

2
Se acostumbra regresar un arreglo, pero como es un tema que todavı́a no revisamos, el
5.2 La lista de registros 240

5.2.2. Eliminación de registros

Tal vez el proceso más complicado es el de eliminar un estudiante, pues se


tienen que verificar varios aspectos de la lista. No es lo mismo eliminar al primer
estudiante que eliminar a alguno que se encuentre en posiciones posteriores a la
primera. Como mantenemos una referencia al último elemento de la lista, también
será distinto eliminar al último pues tendremos que actualizar esta referencia.
Veamos en la figura 5.24 los tres casos.
Figura 5.24 Eliminación de un estudiante

lista
@
Eliminación del último

ultimo Inf o @
Inf o @
Inf o @
Inf o @
@ [] []

lista
@
Eliminación del primero

ultimo Inf o @
Inf o @
Inf o @
Inf o @
@ []

lista
@
Eliminación entre dos registros

ultimo Inf o @
Inf o @
Inf o @
Inf o @
@ []

En el primer caso tenemos que “redirigir” la referencia ultimo a que ahora


apunte hacia el que era el penúltimo registro; para ello tenemos que localizar al
registro anterior al último; también debemos anular la referencia en el penúltimo
registro para que ahı́ termine la lista.

método regresa un objeto que contiene dos valores.


241 Datos estructurados

El segundo caso es mucho más sencillo porque únicamente hay que redirigir la
cabeza de la lista hacia el segundo registro.
El tercer caso coincide con el primero pues también hay que localizar al re-
gistro anterior, para modificar las referencias de éste a que ahora sean al registro
siguiente. En los tres casos, de alguna manera tenemos que tener la referencia del
anterior –a menos que únicamente haya un registro en la lista–.
Una vista general del algoritmo para eliminar un registro se encuentra en la
figura 5.25. No ponemos en el algoritmo la verificación de que los argumentos sean
correctos, como es el caso de que nos proporcionen una lista vacı́a o una referencia
nula para el estudiante a eliminar. En la figura 5.26 desarrollamos con mayor
detalle la parte correspondiente a cuando el registro a eliminar no es el primero.
Figura 5.25 Eliminación de un registro en una lista (vista general)
$ $
'
'
' '
'
'
' '
'
&Modifica a lista para que
'
'
'
'
'
' Es el primero apunte al que sigue
'
' '
'
'
'
'
' '
%
Avisa que sı́ se pudo
'
'
'
'
'
' À
'
'
'
'
' $ $
'
'
' '
' '
'
'
' '
'
' '
'
'
' '
' '
'
'
Localizar al que se desea eliminar
Eliminar & '
' '
' y registrar quién es el anterior
un '
'
' '
&
estudiante '
' '
'
'
'
'
' '
' Proceso de
'
Es el último
'
' '
' eliminación '
' À
'
' '
& '
'
'
' '
'
'
'
'
' Es el primero '
'
'
' '
'
' %Es el último
'
'
' '
'
'
' '
'
' !
'
'
' '
'
'
' '
'
' Eliminado Avisa que SÍ se pudo
'
'
' '
' À
'
' '
'
'
'
'
' '
' !
% %Eliminado Avisa que NO se pudo

Hay que distinguir los tres casos. En primera instancia únicamente pregunta-
mos si es el primero de la lista o no. Si es el primero, la eliminación es sumamente
sencilla, ya que únicamente hay que actualizar la cabeza de la lista. Si no es el
primero, ya sea que esté en medio o al final, deberemos localizar al registro que
deseamos eliminar, registrando cada vez que avanzamos en la lista, la referencia al
5.2 La lista de registros 242

registro anterior; esto se debe a que queremos modificar la referencia en el registro


anterior. Una vez localizado el registro a eliminar y el anterior a éste, procedemos
a manejar el caso de que sea el último.
Figura 5.26 Eliminación de un registro en una lista (vista a detalle)
$ $ $
'
'
' '
' '
'
'
' '
' '
&Colocarse en el que
'
' '
'
'
'
'
' '
' Inicialización sigue a lista
'
' '
' '
'
'
'
' '
'
' %Registrar a lista co-
'
'
' & mo el anterior
'
'
' Localizar al que $ "
'
'
' se desea eliminar '' '
' NO es moverse al
'
' '
'
' '
'
'
' y registrar quién '
' Revisar la lista & À
siguiente
'
'
' '
es el anterior '
' '
'
(mientras haya Y
"
'
'
' '
' no lo encuentre) ' '
'
'
' '
% '
% Marcar
'
'
'
SÍ es
encontrado
'
'
' $
'
& &
%ultimoÐanterior
Proceso de Es el último
eliminación '
'
'
'
'
'
'
' À
'
'
'
'
'
'
'
'
' !
'
'
' Es el último
'
'
'

'
' $
'
'
' &
'
'
' Encontrado
'
'
' %Brincar a actual en la lista
'
'
' À
(anterior.setSiguiente(actual.getSiguiente()))
'
'
'
'
'
' !
%Encontrado ∅

Como estamos trabajando con una lista ligada donde todas las referencias
apuntan hacia el final de la lista, cuando queramos trabajar con uno de los registros
(objetos) de la lista tendremos que tener la referencia a ese objeto, y lo ideal
es que usemos la referencia que se encuentra en el registro anterior. Si lo que
recibe nuestro método es la referencia del registro anterior habrá posibilidad de
modificar la referencia al actual, lo que requerimos para eliminar al registro actual
o insertar un registro entre el anterior y el actual. Por estas razones codificaremos
un método cuyo único objetivo es proporcionar la referencia del registro anterior
al que buscamos. Vamos a seguir para ello el algoritmo general que dimos en la
243 Datos estructurados

figura 5.19, donde lo que queremos que cumpla es que su referencia al siguiente
corresponda al registro que estamos buscando. La codificación de este método se
encuentra en el listado 5.31.

Código 5.31 Método que localiza al elemento anterior en una lista


39500 /∗ ∗
39600 ∗ Método <code>b u s c a A n t e r i o r </code> b u s c a a l r e g i s t r o que
39700 ∗ t o c a a n t e s d e l que s e d e s e a . Sabemos que e l que s e d e s e a
39800 ∗ no e s e l p r i m e r o de l a l i s t a y que l a l i s t a no e s t á v a cı́ a .
39900 ∗ @param q u i e n t i p o <code>E s t u d i a n t e </code >, e l r e g i s t r o a
40000 ∗ buscar .
40100 ∗ @ r e t u r n t i p o <code>E s t u d i a n t e </code >, l a r e f e r e n c i a a l
40200 ∗ anterior .
40300 ∗/
40400 private Estudiante buscaAnterior ( Estudiante quien ) {
40500 Estudiante actual = l i s t a ;
40600 w h i l e ( a c t u a l . g e t S i g u i e n t e ( ) != n u l l &&
40700 a c t u a l . g e t S i g u i e n t e ( ) != q u i e n ) {
40800 actual = actual . getSiguiente ();
40900 }
41000 return a c t u a l ;
41100 }

Estamos suponiendo que el usuario ya localizó la referencia del registro que


desea. Este método sólo va a ser invocado desde métodos de la clase, no por el
usuario.

Usamos el método buscaAnterior para simplificar el código del método elimi-


5.2 La lista de registros 244

naAlumno, que se encuentra en el listado 5.32.


Código 5.32 Eliminación de un registro en una lista CursoListas (1/2)

41300 /∗ ∗
41400 ∗ Método <code>e l i m i n a A l u m n o </code> e l i m i n a a l r e g i s t r o c uy a
41500 ∗ r e f e r e n c i a s e p a s a como argumento .
41600 ∗ @param q u i e n de t i p o <code>E s t u d i a n t e </code >, l a r e f e r e n c i a d e l
41700 ∗ registro a eliminar .
41800 ∗/
41900 p u b l i c boolean e l i m i n a A l u m n o ( E s t u d i a n t e q u i e n ) {
42000 /∗ V e r i f i c a r que l a l i s t a no e s t é v a cı́ a y que l a r e f e r e n c i a
42100 ∗ s e a c o r r e c t a ∗/
42200 i f ( q u i e n == n u l l | | l i s t a == n u l l ) {
42300 System . o u t . p r i n t l n ( " Parámetros inválidos al "
42400 + " tratar de eliminar " ) ;
42500 return f a l s e ;
42600 }
42700 i f ( l i s t a == q u i e n ) { // Es l a p r i m e r a r e f e r e n c i a
42800 i f ( u l t i m o == q u i e n ) { // e s l a ú n i c a r e f e r e n c i a
42900 ultimo = null ;
43000 }
43100 l i s t a = l i s t a . getSiguiente ();
43200 numRegs ;
43300 return true ;
43400 }

Código 5.32 Eliminación de un registro en una lista CursoListas (2/2)

43500 // E n c o n t r a r a l a n t e r i o r
43600 Estudiante a n t e r i o r = buscaAnteriorAQuien ( l i s t a , quien ) ;
43700 // V e r i f i c a r que s e a e l que b u s c a b a
43800 i f ( a n t e r i o r . g e t S i g u i e n t e ( ) == q u i e n ) { // l o e n c o n t r é
43900 a n t e r i o r . s e t S i g u i e n t e ( quien . getSiguiente ( ) ) ;
44000 i f ( u l t i m o == q u i e n ) {
44100 ultimo = anterior ;
44200 }
44300 numRegs ;
44400 return true ;
44500 }
44600 // No l o e n c o n t r é en l a l i s t a
44700 System . o u t . p r i n t l n ( "No se encontró a:\n"
44800 + quien . armaRegistro ( ) ) ;
44900 return f a l s e ;
45000 }

Estamos aprovechando que podemos salir de un método a la mitad del mismo


245 Datos estructurados

para que quede un esquema más simple de la programación, como en el caso de


que el registro a eliminar sea el primero.
No hay mucho que aclarar en este método, ya que corresponde directamente
al algoritmo. En general, se verifica que el estudiante a eliminar no sea el primero;
si lo es, se le elimina; si no lo es, se procede a buscar su ubicación en la lista,
manteniendo siempre la referencia al anterior, para poder modificar su referencia
al estudiante que se desea eliminar.

Inserción en orden alfabético


En general, cuando deseamos una lista de nombres, lo común es que la desee-
mos en orden alfabético (o lexicográfico). Para ello, antes de agregar un registro
debemos encontrar el lugar que le toca en la lista. Nuevamente, si el nombre es
lexicográficamente menor (va antes en el alfabeto) que el primero, lo insertamos al
principio de la lista; si es mayor que el último, lo insertamos al final de la lista; si
no tenemos ninguno de estos dos casos, deberemos recorrer la lista hasta encontrar
el que va antes y el que va después, para modificar las referencias como se muestra
en la figura 5.27. En esta figura tenemos con lı́neas punteadas cómo deben quedar
las referencias después de insertar un registro en el lugar que le corresponde de
acuerdo al nombre3 .

Figura 5.27 Inserción ordenada de registros

Variables Heap
@
@ @ @
lista Federico Pedro Tania []
@
anterior
@ @
Mario []
nuevo

3
En este caso aparece únicamente la cadena del primer nombre como si estuviera directamente
en el registro, aunque sabemos que, por tratarse de cadenas, lo que aparece ahı́ es una referencia.
5.2 La lista de registros 246

El algoritmo para lograr esta inserción aparece en la figura 5.28.

Figura 5.28 Algoritmo para insertar a un estudiante en orden


$ $
'
'
' '
' sNombre Ð cadena a comparar en nuevo
'
' '
'
'
'
'
' '
'
' sLista Ð cadena a comparar en lista
'
'
' '
' $
'
' '
'
'
'
'
'
' &sNombre   sLista &Agregar aldeprincipio
'
'
'
' Inicio %
la lista
'
'
' '
' Salir
'
' '
'
'
'
'
'
'
'
'
'
'
` #
'
'
' '
'
' '
' sNombre   sLista
Colocarse al principio de la lista
'
'
' '
%
'
'
' Comparar
'
'
& $
Agrega '
' sLista Ð nombre que sigue al actual
en orden '
' '
'
'
'
' '
' $
'
'
' Encuentra '
' '
&Agrega entre actual y
'
' '
&
(mientras haya sNombre   sLista '
'
'
'
lugar actual.getSiguiente()
'
' %Salir
'
'
' y siga uno ''
'
'
' '
'
'
'
'
'
'
menor) '
'
' ` !
' '
%
'
'
'
'
sNombre   sLista Pasar al siguiente
'
'
'
'
' $ $
'
'
' '
& &
'
'
'
'
'
%
Final
' %Agregar al final
%Llegó al final de la lista
de la lista

Para este algoritmo, la implementación en Java se encuentra en el listado 5.33.


247 Datos estructurados

Código 5.33 Inserción de un registro en orden lexicográfico CursoListas

47400 /∗ ∗
47500 ∗ Método <code>agregaEnOrden </code >: I n s e r t a un nuevo r e g i s t r o en
47600 ∗ o r d e n l e x i c o g r á f i c o .
47700 ∗ @param nuevo t i p o <code>E s t u d i a n t e </code >: r e g i s t r o a i n s e r t a r .
47800 ∗/
47900 p u b l i c v o i d agregaEnOrden ( E s t u d i a n t e nuevo ) {
48000 i f ( l i s t a == n u l l ) {
48100 l i s t a = u l t i m o = nuevo ;
48200 numRegs++;
48300 return ;
48400 }
48500 S t r i n g sNuevo = nuevo . getNombre ( ) . t r i m ( ) ;
48600 S t r i n g s L i s t a = l i s t a . getNombre ( ) . t r i m ( ) ;
48700 i n t compara = sNuevo . c o m p a r e T o I g n o r e C a s e ( s L i s t a ) ;
48800 i f ( compara < 0 ) { // Le t o c a e l p r i m e r o
48900 nuevo . s e t S i g u i e n t e ( l i s t a ) ;
49000 l i s t a = nuevo ;
49100 numRegs++;
49200 return ;
49300 }
49400 /∗ Hay a l menos uno y no l e t o c a a n t e s de é l ∗/
49500 E s t u d i a n t e a c t u a l = l i s t a ; // C o l o c a r s e a l p r i n c i p i o de l a l i s t a
49600 compara = sNuevo . c o m p a r e T o I g n o r e C a s e ( s L i s t a ) ; // T i e n e que d a r >0
49700 w h i l e ( a c t u a l . g e t S i g u i e n t e ( ) != n u l l && compara >= 0 ) {
49800 s L i s t a = a c t u a l . g e t S i g u i e n t e ( ) . getNombre ( ) . t r i m ( ) ;
49900 compara = sNuevo . c o m p a r e T o I g n o r e C a s e ( s L i s t a ) ;
50000 i f ( compara < 0 ) { // E l nuevo e s menor
50100 nuevo . s e t S i g u i e n t e ( a c t u a l . g e t S i g u i e n t e ( ) ) ;
50200 a c t u a l . s e t S i g u i e n t e ( nuevo ) ;
50300 numRegs++;
50400 return ;
50500 }
50600 actual = actual . getSiguiente ();
50700 }
50800 // Cubre e l c a s o de que l e t o q u e a l f i n a l
50900 i f ( a c t u a l . g e t S i g u i e n t e ( ) == n u l l ) { // l e t o c a a l ú l t i m o
51000 a c t u a l . s e t S i g u i e n t e ( nuevo ) ;
51100 u l t i m o = nuevo ;
51200 numRegs++;
51300 }
51400 }
5.2 La lista de registros 248

Prueba de la clase CursoListas


Ya tenemos todo lo que deseábamos tener –y más, pues estamos agregando
de tres distintas maneras–. Programemos el método main para esta clase que
simplemente pruebe los métodos que tenemos. El método main queda como se
muestra en el listado 5.34.

Código 5.34 Prueba de la clase CursoListas CursoListas (1/2)

51600 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
51700 /∗ Prueba de l o s métodos ∗/
51800 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
51900 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
52000 C u r s o L i s t a s i c c 1 = new C u r s o L i s t a s ( "7004" ) ;
52100 // Agregamos a l g u n o s e s t u d i a n t e s a l p r i n c i p i o de l a l i s t a
52200 i c c 1 . agregaAlumno ( "Viso Elisa " , 1 2 2 , " 06730268 " ,
52300 " elisa@ciencias " ) ;
52400 i c c 1 . agregaAlumno ( " Magidin Arturo " , 1 2 2 , " 089428452 " ,
52500 " arturo@ams " ) ;
52600 i c c 1 . agregaAlumno ( " Torres Maria " , 1 2 7 , " 301245329 " ,
52700 " torresm@ciencias " ) ;
52800 i c c 1 . agregaAlumno ( " Barajas Miguel " , 1 0 6 , "000" , "no tiene " ) ;
52900 System . o u t . p r i n t l n ( i c c 1 . dameCurso ( ) ) ;
53000 System . o u t . p r i n t l n ( i c c 1 . d a m e L i s t a ( ) ) ;
53100 // Probamos a h o r a que a g r e g u e en o r d e n
53200 C u r s o L i s t a s i c c O r d = new C u r s o L i s t a s ( "7004" ) ;
53300 Estudiante laLista = icc1 . getLista ( ) ;
53400 E s t u d i a n t e nuevo ;
53500 C u r s o L i s t a s a l R e v e s = new C u r s o L i s t a s ( "7005" ) ;
53600 l a L i s t a = iccOrd . g e t L i s t a ( ) ;
53700 w h i l e ( l a L i s t a != n u l l ) {
53800 a l R e v e s . agregaAlumno ( l a L i s t a . getNombre ( ) ,
53900 laLista . getCarrera () ,
54000 l a L i s t a . getCuenta ( ) ,
54100 l a L i s t a . getCorreo ( ) ) ;
54200 laLista = laLista . getSiguiente ();
54300 }
54400 System . o u t . p r i n t l n ( "al reves :\n" +
54500 alReves . dameLista ( ) ) ;
54600 C u r s o L i s t a s a l F i n a l = new C u r s o L i s t a s ( "7006" ) ;
54700 a l F i n a l . agregaAlumno ( "Viso Elisa " , 1 2 2 , " 06730268 " ,
54800 " elisa@ciencias " ) ;
54900 a l F i n a l . agregaAlumno ( " Magidin Arturo " , 1 2 2 , " 089428452 " ,
55000 " arturo@ams " ) ;
55100 a l F i n a l . agregaAlumno ( " Torres Maria " , 1 2 7 , " 301245329 " ,
55200 " torresm@ciencias " ) ;
55300 a l F i n a l . agregaAlumno ( " Barajas Miguel " , 1 0 6 , "000" , "no tiene " ) ;
55400 System . o u t . p r i n t l n ( a l F i n a l . dameCurso ( ) ) ;
249 Datos estructurados

Código 5.34 Prueba de la clase CursoListas CursoListas (2/2)

55500 /∗ E l i m i n a n d o r e g i s t r o s de l a l i s t a ∗/
55600 E s t u d i a n t e q u i e n = a l F i n a l . l o c a l i z a A l u m n o ( "Viso" ,NOMBRE) ;
55700 i f ( q u i e n != n u l l ) {
55800 a l F i n a l . eliminaAlumno ( quien ) ;
55900 }
56000 System . o u t . p r i n t l n ( " Después de eliminar a Viso :\n"
56100 + a l F i n a l . dameCurso ( ) ) ;
56200 q u i e n = a l F i n a l . l o c a l i z a A l u m n o ( " Barajas " ,NOMBRE) ;
56300 i f ( q u i e n != n u l l ) {
56400 a l F i n a l . eliminaAlumno ( quien ) ;
56500 }
56600 System . o u t . p r i n t l n ( " Después de eliminar a Barajas :\n"
56700 + a l F i n a l . dameCurso ( ) ) ;
56800 C u r s o L i s t a s enOrden = new C u r s o L i s t a s ( "7006" ) ;
56900 enOrden . agregaEnOrden ( new E s t u d i a n t e ( "Viso Elisa " ,
57000 " elisa@ciencias " ,
57100 " 06730268 " ,
57200 122));
57300 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Magidin Arturo " ,
57400 " arturo@ams " ,
57500 " 089428452 " ,
57600 122));
57700 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Torres Maria " ,
57800 " torresm@ciencias " ,
57900 " 301245329 " ,
58000 127));
58100 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Barajas Miguel " ,
58200 "no tiene " ,
58300 "000" ,
58400 106));
58500 System . o u t . p r i n t l n ( enOrden . dameCurso ( ) ) ;
58600 }

La salida que se produce con la ejecución del método main de la clase se muestra
en el listado 5.29.
Figura 5.29 Ejecución del método main de la clase CursoListas Salida (1/3)

Grupo : 7004 Núm . de alumnos : 4

000000000 Fı́ s i c a Barajas Miguel


no t i e n e
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
5.2 La lista de registros 250

Figura 5.30 Ejecución del método main de la clase CursoListas Salida (2/3)

006730268 M a t e má t i c a s Viso E l i s a


elisa@ciencias
000000000 Fı́ s i c a Barajas Miguel
no t i e n e
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
006730268 M a t e má t i c a s Viso E l i s a
elisa@ciencias
a l r e v é s :
No hay e s t u d i a n t e s r e g i s t r a d o s en e l g r u p o
Grupo : 7006 Núm . de alumnos : 4

000000000 Fı́ s i c a Barajas Miguel


no t i e n e
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
006730268 M a t e má t i c a s Viso E l i s a
elisa@ciencias
Después de e l i m i n a r a V i s o :
Grupo : 7006 Núm . de alumnos : 3

000000000 Fı́ s i c a Barajas Miguel


no t i e n e
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
Después de e l i m i n a r a B a r a j a s :
Grupo : 7006 Núm . de alumnos : 2

301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
Grupo : 7006 Núm . de alumnos : 4
251 Datos estructurados

Figura 5.30 Ejecución del método main de la clase CursoListas Salida (3/3)

000000000 Fı́ s i c a Barajas Miguel


no t i e n e
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams

301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
006730268 M a t e má t i c a s Viso E l i s a
elisa@ciencias

Ayudándose de los enunciados que imprimen para localizar cada una de las
lı́neas que lo hacen, se puede seguir la ejecución de la clase de manera sencilla,
por lo que ya no ilustraremos esta ejecución.

5.2.3. La clase MenuCursoListas

La programación del menú para tener acceso a la base de datos del curso es
similar al caso en que los registros eran cadenas, excepto que ahora para agregar
a cualquier estudiante hay que construir un objeto de la clase Estudiante. El algo-
ritmo es el mismo, por lo que ya no lo mostramos. La programación se encuentra
en el listado 5.34 en la siguiente página.
Es en esta clase donde realmente se van a crear objetos nuevos para poderlos
ir enlazando en la lista. Por ejemplo, para agregar un estudiante, una vez que
tenemos los datos (lı́neas 6500-6800 en el listado 5.34 en la siguiente página),
procedemos a invocar al método agregaAlumno de la clase CursoListas. Este método
tiene como argumento un objeto, que es creado en el momento de invocar a agrega
– ver lı́nea 7000 del listado 5.34 en la siguiente página. También podemos pedir
que agregue a un estudiante nuevo en el lugar que le toca alfabéticamente, si a-
gregamos la opción de agregar en orden –lı́neas 7200 a 7800–. Estos son los únicos
métodos en los que se crean objetos, y esto tiene sentido, ya que se requiere crear
objetos sólo cuando se desea agregar a un estudiante.
Para el caso de los métodos de la clase CursoListas que regresan una referencia
a un objeto de la clase Estudiante, es para lo que se declaró la variable cual – lı́nea
5500 del listado 5.34 en la siguiente página. En estos casos el objeto ya existe, y
lo único que hay que pasar o recibir son las variables que contienen la referencia.
5.2 La lista de registros 252

Código 5.34 Menú para el manejo de la lista MenuCursoListas (1/6)


100 package C o n s u l t a s L i s t a s ;
200 import j a v a . u t i l . S c a n n e r ;
300 import u t i l e s . C a t a l o g o C a r r e r a s ;
400 /∗ ∗
500 ∗ c l a s s MenuCurso e s l a c l a s e de u s o p a r a l a b a s e de d a t o s
600 ∗ d e l grupo .
700 ∗ C r e a t e d : Lun Mar 8 0 7 : 5 2 : 2 6 2 0 1 0 .
800 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a . f c i e n c i a s . unam . mx”>
900 ∗ E l i s a V i s o G u r o v i c h </a>
1000 ∗ @version 2.0
1100 ∗/
1200 public class MenuCursoListas {
1300 p r i v a t e s t a t i c f i n a l i n t FIN = 0 ,
1400 AGREGA = 1 ,
1500 BUSCA = 2 ,
1600 LISTA = 3 ,
1700 ELIGE = 4 ,
1800 QUITA = 5 ,
1900 MUESTRA = 6 ,
2000 AGREGAENORDEN = 7 ;
2100 p r i v a t e s t a t i c f i n a l S t r i n g elMenu =
2200 " Elige una opción :"
2300 + "[0] Terminar \n"
2400 + "[1] Agregar estudiante \n"
2500 + "[2] Buscar estudiante \n"
2600 + "[3] Listar todos \n"
2700 + "[4] Listar los que cazan con ...\n"
2800 + "[5] Eliminar estudiante \n"
2900 + "[6] Mostrar estudiante \n"
3000 + "[7] Agregar en orden \n" ;
3100 p r i v a t e s t a t i c f i n a l i n t MAXCAMPo = 4 ;
3200 private int opcion = 0;
3300 p r i v a t e S t r i n g s o p c i o n = "" ;
3400 p r i v a t e s t a t i c f i n a l S t r i n g sCampos =
3500 "(1) Nombre "
3600 + "(2) Carrera \n"
3700 + "(3) Correo \n"
3800 + "(4) Num. de cuenta " ;
3900
4000 /∗ ∗
4100 ∗ Método <code>daMenu</code >: Maneja l a i n t e r a c c i ó n con e l
4200 ∗ u s u a r i o a t r a v é s de un menú .
4300 ∗ @param c o n s t i p o <code>S c a n n e r </code >: d i s p o s i t i v o de l e c t u r a .
4400 ∗ @param miCurso t i p o <code>C u r s o L i s t a </code >: o b j e t o d e l c u r s o .
4500 ∗ @ r e t u r n t i p o <code>i n t </code >: ú l t i m a o p c i ó n s e l e c c i o n a d a .
4600 ∗/
253 Datos estructurados

Código 5.34 Menú para el manejo de la lista MenuCursoListas (2/6)

4700 p u b l i c i n t daMenu ( S c a n n e r cons , C u r s o L i s t a s miCurso ) {


4800 s o p c i o n = "" ;
4900 opcion = 0;
5000 System . o u t . p r i n t l n ( elMenu ) ;
5100 System . o u t . p r i n t l n ( " Elige una opcion -->\t" ) ;
5200 sopcion = cons . nextLine ( ) ;
5300 o p c i o n = " 0123456 " . i n d e x O f ( s o p c i o n . c h a r A t ( 0 ) ) ;
5400 S t r i n g quien , subcad ;
5500 Estudiante cual ;
5600 S t r i n g nombre = n u l l ;
5700 S tr i n g cuenta = null ;
5800 String correo = null ;
5900 int carrera = 0;
6000 boolean b r e s p = f a l s e ;
6100 switch ( opcion ) {
6200 case FIN : System . o u t . p r i n t l n ( " Mucho gusto en haberle servido " ) ;
6300 r e t u r n  1;
6400 case AGREGA :
6500 nombre = pideNombre ( c o n s ) ;
6600 cuenta = pideCuenta ( cons ) ;
6700 c o r r e o = pideCorreo ( cons ) ;
6800 c a r r e r a = p i d e C a r r e r a ( cons ) ;
6900 miCurso . agregaAlumno ( new E s t u d i a n t e ( nombre , c o r r e o ,
7000 cuenta , c a r r e r a ) ) ;
7100 r e t u r n AGREGA ;
7200 case AGREGAENORDEN :
7300 nombre = pideNombre ( c o n s ) ;
7400 cuenta = pideCuenta ( cons ) ;
7500 c o r r e o = pideCorreo ( cons ) ;
7600 c a r r e r a = p i d e C a r r e r a ( cons ) ;
7700 miCurso . agregaEnOrden ( new E s t u d i a n t e ( nombre , c o r r e o ,
7800 cuenta , c a r r e r a ) ) ;
7900 r e t u r n AGREGAENORDEN ;
8000 case BUSCA :
8100 i f ( miCurso . g e t L i s t a ( ) == n u l l ) {
8200 System . o u t . p r i n t l n ( "No hay nadie registrado todavı́a " ) ;
8300 r e t u r n BUSCA ;
8400 }
8500 // La l i s t a d e l g r u p o no e s t á v a cı́ a
8600 System . o u t . p r i n t l n ( "Dame la cadena a buscar : " ) ;
8700 subcad = cons . n e x t L i n e ( ) ;
8800 i f ( s u b c a d . l e n g t h ( ) == 0 ) {
8900 System . o u t . p r i n t l n ( "Hubo un error de entrada " ) ;
9000 r e t u r n BUSCA ;
9100 }
9200 System . o u t . p r i n t l n ( " Ahora dime en cuál campo quieres "
9300 + "que busque :" ) ;
5.2 La lista de registros 254

Código 5.34 Menú para el manejo de la lista MenuCursoListas (3/6)

9400 System . o u t . p r i n t l n ( sCampos ) ;


9500 i n t campo = c o n s . n e x t I n t ( ) ;
9600 i f ( campo <= 0 | | campo > MAXCAMPO) {
9700 System . o u t . p r i n t l n ( " Datos erróneos " ) ;
9800 r e t u r n BUSCA ;
9900 }
10000 c u a l = miCurso . l o c a l i z a A l u m n o ( subcad , campo ) ;
10100 i f ( c u a l == n u l l ) {
10200 System . o u t . p r i n t l n ( "No existe registro con esta cadena " ) ;
10300 r e t u r n BUSCA ;
10400 }
10500 do {
10600 System . o u t . p r i n t l n ( "El alumnos buscado es :\t"
10700 + miCurso . a r m a R e g i s t r o ( c u a l ) ) ;
10800 System . o u t . p r i n t l n ( " Deseas ver al siguiente ?" ) ;
10900 b r e s p = c o n s . n e x t L i n e ( ) . e q u a l s ( "Si" ) ;
11000 i f (! bresp )
11100 r e t u r n BUSCA ;
11200 i f ( c u a l == n u l l ) {
11300 System . o u t . p r i n t l n ( "Se acabó la base de datos " ) ;
11400 r e t u r n BUSCA ;
11500 }
11600 c u a l = miCurso . l o c a l i z a A l u m n o ( subcad , campo , c u a l ) ;
11700 } w h i l e ( c u a l != n u l l ) ;
11800 System . o u t . p r i n t l n ( "No hay mas registros con esa subcadena " ) ;
11900 r e t u r n BUSCA ;
12000 case LISTA :
12100 System . o u t . p r i n t l n ( miCurso . d a m e L i s t a ( ) ) ;
12200 r e t u r n LISTA ;
12300 case ELIGE :
12400 System . o u t . p r i n t l n ( "Qué subcadena deben cazar :" ) ;
12500 subcad = cons . n e x t L i n e ( ) ;
12600 i f ( s u b c a d . l e n g t h ( ) == 0 ) {
12700 System . o u t . p r i n t l n ( "No se dio una cadena válida "
12800 + " a buscar " ) ;
12900 r e t u r n ELIGE ;
13000 }
13100 System . o u t . p r i n t ( "¿En qué campo ?"
13200 + "(1) nombre \n(2) cuenta \n"
13300 + "(3) correo \n(4) carrera \n"
13400 + " campo --> " ) ;
13500 S t r i n g strCampo = c o n s . n e x t L i n e ( ) . t r i m ( ) ;
13600 i f ( strCampo . l e n g t h ( ) == 0 ) {
13700 System . o u t . p r i n t l n ( "No se dio un campo válido "
13800 + " donde buscar " ) ;
13900 r e t u r n ELIGE ;
14000 }
14100 System . o u t . p r i n t l n ( miCurso . losQueCazanCon ( subcad ,
14200 I n t e g e r . p a r s e I n t ( strCampo ) ) ) ;
14300 r e t u r n ELIGE ;
255 Datos estructurados

Código 5.34 Menú para el manejo de la lista MenuCursoListas (4/6)


14400 case QUITA :
14500 System . o u t . p r i n t l n ( "Dame el alumno a eliminar " ) ;
14600 quien = cons . nextLine ( ) ;
14700 i f ( q u i e n . l e n g t h ( ) == 0 ) {
14800 System . o u t . p r i n t l n ( "Me diste una cadena inválida " ) ;
14900 r e t u r n QUITA ;
15000 }
15100 bresp = false ;
15200 do {
15300 c u a l = miCurso . l o c a l i z a A l u m n o ( q u i e n , E s t u d i a n t e .NOMBRE) ;
15400 i f ( c u a l == n u l l ) {
15500 System . o u t . p r i n t l n ( "Ya no hay más con este campo " ) ;
15600 r e t u r n QUITA ;
15700 }
15800 System . o u t . p r i n t ( " Eliminar a *"
15900 + miCurso . daNombre ( c u a l ) . t r i m ( )
16000 + "*, Si , No --> " ) ;
16100 b r e s p = c o n s . n e x t L i n e ( ) . e q u a l s ( "Si" ) ;
16200 i f ( bresp ) {
16300 i f ( miCurso . e l i m i n a A l u m n o ( c u a l ) ) {
16400 System . o u t . p r i n t l n ( " Alumno eliminado del curso " ) ;
16500 r e t u r n QUITA ;
16600 }
16700 }
16800 System . o u t . p r i n t l n ( " Deseas ver el siguiente ?" ) ;
16900 b r e s p = c o n s . n e x t L i n e ( ) . e q u a l s ( "Si" ) ;
17000 } w h i l e ( b r e s p && c u a l != miCurso . g e t U l t i m o ( )
17100 && c u a l != n u l l ) ;
17200 System . o u t . p r i n t l n ( "No se eliminó a nadie " ) ;
17300 r e t u r n QUITA ;
17400 case MUESTRA:
17500 System . o u t . p r i n t l n ( "Dame una subcadena a buscar " ) ;
17600 quien = cons . nextLine ( ) ;
17700 i f ( q u i e n . l e n g t h ( ) == 0 ) {
17800 System . o u t . p r i n t l n ( "Dato invalido " ) ;
17900 r e t u r n MUESTRA;
18000 }
18100 c u a l = miCurso . l o c a l i z a A l u m n o ( q u i e n , E s t u d i a n t e .NOMBRE) ;
18200 i f ( c u a l != n u l l ) {
18300 System . o u t . p r i n t l n ( miCurso . a r m a R e g i s t r o ( c u a l ) ) ;
18400 } else {
18500 System . o u t . p r i n t l n ( "No hay registro con esta subcadena " ) ;
18600 }
18700 r e t u r n MUESTRA;
18800 default :
18900 System . o u t . p r i n t l n ( " Opcion no existente " ) ;
19000 return 0;
19100 } // s w i t c h
19200 } // daMenu
5.2 La lista de registros 256

Código 5.34 Menú para el manejo de la lista MenuCursoListas (5/6)

19300 /∗ ∗
19400 ∗ Método <code>pideNombre </code >, p i d e e l nombre d e l e s t u d i a n t e .
19500 ∗ @param c o n s v a l o r de t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
19600 ∗ del usuario .
19700 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
19800 ∗/
19900 p r i v a t e S t r i n g pideNombre ( S c a n n e r c o n s ) {
20000 System . o u t . p r i n t l n ( "Dame el nombre :\t" ) ;
20100 S t r i n g nombre = c o n s . n e x t L i n e ( ) ;
20200 r e t u r n nombre ;
20300 }
20400
20500 /∗ ∗
20600 ∗ Método <code>p i d e C u e n t a </code >, p i d e e l número de c u e n t a d e l
20700 ∗ estudiante .
20800 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
20900 ∗ del usuario .
21000 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
21100 ∗/
21200 private S t r i n g pideCuenta ( Scanner cons ) {
21300 System . o u t . p r i n t l n ( "Dame el numero de cuenta :\t" ) ;
21400 S t r i n g cuenta = cons . nextLine ( ) ;
21500 return cuenta ;
21600 }
21700
21800 /∗ ∗
21900 ∗ Método <code>p i d e C o r r e o </code >, p i d e e l c o r r e o d e l e s t u d i a n t e .
22000 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
22100 ∗ del usuario .
22200 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
22300 ∗/
22400 private S t r i n g pideCorreo ( Scanner cons ) {
22500 System . o u t . p r i n t l n ( "Dame el correo :\t" ) ;
22600 S t r i n g c o r r e o = cons . nextLine ( ) ;
22700 return correo ;
22800 }
22900
23000 /∗ ∗
23100 ∗ Metodo <code>p i d e C a r r e r a </code >, p i d e e l c o r r e o d e l e s t u d i a n t e .
23200 ∗ @param c o n s v a l o r de t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
23300 ∗ del usuario .
23400 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >, e l d a t o p r o p o r c i o n a d o .
23500 ∗/
23600 private i nt p i d e C a r r e r a ( Scanner cons ) {
23700 int carrera = 0;
257 Datos estructurados

Código 5.34 Menú para el manejo de la lista MenuCursoListas (6/6)


23800 while ( ! CatalogoCarreras . e s C a r r e r a ( c a r r e r a )) {
23900 System . o u t . p r i n t l n ( "Las carreras registradas son:" ) ;
24000 CatalogoCarreras . muestraCatalogo ( ) ;
24100 System . o u t . p r i n t l n ( "Dame la carrera :" ) ;
24200 c a r r e r a = cons . n e x t I n t ( ) ;
24300 c o n s . n e x t L i n e ( ) ; // Para u s a r e l r e t o r n o de c a r r o
24400 i f (! CatalogoCarreras . esCarrera ( carrera )) {
24500 System . o u t . p r i n t l n ( " Carrera incorrecta ."
24600 + " Vuelve a proporcionar :" ) ;
24700 }
24800 } // w h i l e
24900 return c a r r e r a ;
25000 }
25100 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
25200 /∗ ∗∗∗∗∗ Prueba de l o s métodos ∗∗∗∗∗ ∗/
25300 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
25400 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
25500 S c a n n e r c o n s = new S c a n n e r ( System . i n ) ;
25600 Estudiante l i s t i t a =
25700 new E s t u d i a n t e ( " Aguilar Solis Aries Olaf ",
25800 " aaguilar " , " 400001528 " , 1 2 2 ) ;
25900 l i s t i t a . s e t S i g u i e n t e ( new
26000 E s t u d i a n t e ( "Cruz Cruz Gil Noé ",
26100 " ncruz " , " 098034011 " , 1 0 7 ) ) ;
26200 l i s t i t a . getSiguiente ().
26300 s e t S i g u i e n t e ( new
26400 E s t u d i a n t e ( " Garcia Villafuerte Israel ",
26500 " igarcia " , " 098159820 " , 1 0 1 ) ) ;
26600 l i s t i t a . getSiguiente (). getSiguiente ().
26700 s e t S i g u i e n t e ( new
26800 E s t u d i a n t e ( " Hubard Escalera Alfredo ",
26900 " ahubard " , " 099586197 " , 2 0 1 ) ) ;
27000 l i s t i t a . getSiguiente (). getSiguiente (). getSiguiente ().
27100 g e t S i g u i e n t e ( ) . s e t S i g u i e n t e ( new
27200 E s t u d i a n t e ( " Tapia Vazquez Rogelio " ,
27300 " rtapia " , " 098162075 " , 1 ) ) ;
27400 C u r s o L i s t a s miCurso = new C u r s o L i s t a s ( "7050" , l i s t i t a ) ;
27500 M e n u C u r s o L i s t a s menu = new M e n u C u r s o L i s t a s ( ) ;
27600 int resp = 0;
27700 do {
27800 r e s p = menu . daMenu ( cons , miCurso ) ;
27900 } w h i l e ( r e s p >= 0 ) ;
28000 System . o u t . p r i n t l n ( "Fue un placer servirte " ) ;
28100 cons . c l o s e ( ) ;
28200 }
28300 }
5.2 La lista de registros 258

Esta ejecución produce la interacción con el usuario que se ve en la figura 5.31.


Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (1/6)

1 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a C o n s u l t a s
2 L i s t a s / MenuCursoListas
3 E l i g e una o p c i ó n :
4 [ 0 ] Terminar
5 [ 1 ] Agregar e s t u d i a n t e
6 [ 2 ] Buscar e s t u d i a n t e
7 [ 3 ] L i s t a r todos
8 [ 4 ] L i s t a r l o s que c a z a n con . . .
9 [5] Eliminar estudiante
10 [ 6 ] Mostrar e s t u d i a n t e
11 [ 7 ] A g r e g a r en o r d e n
12
13 E l i g e una o p c i ó n >
14 7
15 Dame e l nombre :
16 Viso E l i s a
17 Dame e l numero de c u e n t a :
18 067302682
19 Dame e l c o r r e o :
20 elisa@ciencias
21 Las c a r r e r a s r e g i s t r a d a s s o n :
22 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
23 ===============================================
24 101 Actuaria
25 104 C i e n c i a s de l a Computación
26 106 Fı́ s i c a
27 122 M a t e má t i c a s
28 127 C i e n c i a s de l a T i e r r a
29 201 B i o l o gı́ a
30 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
31 =========================================================
32 Dame l a c a r r e r a :
33 104
34 E l i g e una o p c i ó n :
35 [ 0 ] Terminar
36 [ 1 ] Agregar e s t u d i a n t e
37 [ 2 ] Buscar e s t u d i a n t e
38 [ 3 ] L i s t a r todos
39 [ 4 ] L i s t a r l o s que c a z a n con . . .
40 [5] Eliminar estudiante
41 [ 6 ] Mostrar e s t u d i a n t e
42 [ 7 ] A g r e g a r en o r d e n
43
44 E l i g e una o p c i ó n >
45 3
259 Datos estructurados

Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (2/6)

47 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i


48 es Olaf aaguilar
49 098034011 Fı́ s i c a Cruz Cruz G i l Noé
50 ncruz
51 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
52 e Israel igarcia
53 099586197 B i o l o gı́ a Hubard E s c a l e r a A
54 lfredo ahubard
55 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
56 elio rtapia
57 067302682 C i e n c i a s de l a Computación Viso E l i s a
58 elisa@ciencias
59 Acciones a r e a l i z a r :
60 [ 0 ] Terminar
61 [ 1 ] Agregar e s t u d i a n t e
62 [ 2 ] Buscar e s t u d i a n t e
63 [ 3 ] L i s t a r todos
64 [ 4 ] L i s t a r l o s que c a z a n con . . .
65 [5] Eliminar estudiante
66 [ 6 ] Mostrar e s t u d i a n t e
67 [ 7 ] A g r e g a r en o r d e n
68
69 E l i g e una o p c i ó n >
70 1
71 Dame e l nombre :
72 A b rı́ n B a t u l e V i r g i n i a
73 Dame e l numero de c u e n t a :
74 070245672
75 Dame e l c o r r e o :
76 abrin@servidor
77 Las c a r r e r a s r e g i s t r a d a s s o n :
78 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
79 ===============================================
80 101 Actuaria
81 104 C i e n c i a s de l a Computación
82 106 Fı́ s i c a
83 122 M a t e má t i c a s
84 127 C i e n c i a s de l a T i e r r a
85 201 B i o l o gı́ a
86 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
87 =========================================================
88 Dame l a c a r r e r a :
89 122
90 Acciones a r e a l i z a r :
5.2 La lista de registros 260

Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (3/6)

91 [0] Terminar
92 [1] Agregar e s t u d i a n t e
93 [2] Buscar e s t u d i a n t e
94 [3] L i s t a r todos
95 [4] L i s t a r l o s que c a z a n con . . .
96 [5] Eliminar estudiante
97 [6] Mostrar e s t u d i a n t e
98 [7] A g r e g a r en o r d e n
99
100 E l i g e una o p c i ó n >
101 3
102
103 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
104 inia abrin@servidor
105 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
106 es Olaf aaguilar
107 098034011 Fı́ s i c a Cruz Cruz G i l Noé
108 ncruz
109 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
110 e Israel igarcia
111 099586197 B i o l o gı́ a Hubard E s c a l e r a A
112 lfredo ahubard
113 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
114 elio rtapia
115 067302682 C i e n c i a s de l a Computación Viso E l i s a
116 elisa@ciencias
117 Acciones a r e a l i z a r :
118 [ 0 ] Terminar
119 [ 1 ] Agregar e s t u d i a n t e
120 [ 2 ] Buscar e s t u d i a n t e
121 [ 3 ] L i s t a r todos
122 [ 4 ] L i s t a r l o s que c a z a n con . . .
123 [5] Eliminar estudiante
124 [ 6 ] Mostrar e s t u d i a n t e
125 [ 7 ] A g r e g a r en o r d e n
126
127 E l i g e una o p c i ó n >
128 1
129 Dame e l nombre :
130 Torres Morales Mauricio
131 Dame e l numero de c u e n t a :
132 081234567
133 Dame e l c o r r e o :
134 mauri@ciencias
261 Datos estructurados

Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (4/6)

135 Las c a r r e r a s r e g i s t r a d a s s o n :
136 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
137 ===============================================
138 101 Actuaria
139 104 C i e n c i a s de l a Computación
140 106 Fı́ s i c a
141 122 M a t e má t i c a s
142 127 C i e n c i a s de l a T i e r r a
143 201 B i o l o gı́ a
144 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
145 =========================================================
146 Dame l a c a r r e r a :
147 101
148 Acciones a r e a l i z a r :
149 [ 0 ] Terminar
150 [ 1 ] Agregar e s t u d i a n t e
151 [ 2 ] Buscar e s t u d i a n t e
152 [ 3 ] L i s t a r todos
153 [ 4 ] L i s t a r l o s que c a z a n con . . .
154 [5] Eliminar estudiante
155 [ 6 ] Mostrar e s t u d i a n t e
156 [ 7 ] A g r e g a r en o r d e n
157
158 E l i g e una o p c i ó n >
159 3
160
161 081234567 Actuaria T o r r e s M o r a l e s Ma
162 uricio mauri@ciencias
163 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
164 inia abrin@servidor
165 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
166 es Olaf aaguilar
167 098034011 Fı́ s i c a Cruz Cruz G i l Noé
168 ncruz
169 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
170 e Israel igarcia
171 099586197 B i o l o gı́ a Hubard E s c a l e r a A
172 lfredo ahubard
173 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
174 elio rtapia
175 067302682 C i e n c i a s de l a Computación Viso E l i s a
176 elisa@ciencias
177 Acciones a r e a l i z a r :
178 [ 0 ] Terminar
5.2 La lista de registros 262

Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (5/6)

179 [1] Agregar e s t u d i a n t e


180 [2] Buscar e s t u d i a n t e
181 [3] L i s t a r todos
182 [4] L i s t a r l o s que c a z a n con . . .
183 [5] Eliminar estudiante
184 [6] Mostrar e s t u d i a n t e
185 [7] A g r e g a r en o r d e n
186
187 E l i g e una o p c i ó n >>
188 4
189 Qué s u b c a d e n a deben c a z a r :
190 122
191 ¿En qué campo ?
192 ( 1 ) nombre
193 (2) cuenta
194 (3) correo
195 (4) carrera
196 campo > 4
197
198 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
199 inia abrin@servidor
200 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
201 es Olaf aaguilar
202 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
203 e Israel igarcia
204 067302682 M a t e má t i c a s Viso E l i s a
205 elisa@ciencias
206
207 Acciones a r e a l i z a r : [ 0 ] Terminar
208 [ 1 ] Agregar e s t u d i a n t e
209 [ 2 ] Buscar e s t u d i a n t e
210 [ 3 ] L i s t a r todos
211 [ 4 ] L i s t a r l o s que c a z a n con . . .
212 [5] Eliminar estudiante
213 [ 6 ] Mostrar e s t u d i a n t e
214 [ 7 ] A g r e g a r en o r d e n
215
216 E l i g e una o p c i ó n >
217 5
218 Dame e l alumno a e l i m i n a r
219 G a r cı́ a
220 E l i m i n a r a ∗ G a r cı́ a V i l l a f u e r t e I s r a e l ∗ , S i , No > Si
221 Alumno e l i m i n a d o d e l c u r s o
263 Datos estructurados

Figura 5.31 Ejecución en pantalla de la clase MenuCursoLista (6/6)

222 Acciones a r e a l i z a r :
223 [ 0 ] Terminar
224 [ 1 ] Agregar e s t u d i a n t e
225 [ 2 ] Buscar e s t u d i a n t e
226 [ 3 ] L i s t a r todos
227 [ 4 ] L i s t a r l o s que c a z a n con . . .
228 [5] Eliminar estudiante
229 [ 6 ] Mostrar e s t u d i a n t e
230 [ 7 ] A g r e g a r en o r d e n
231
232 E l i g e una o p c i ó n >
233 3
234
235 081234567 Actuaria T o r r e s M o r a l e s Ma
236 uricio mauri@ciencias
237 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
238 inia abrin@servidor
239 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
240 es Olaf aaguilar
241 098034011 Fı́ s i c a Cruz Cruz G i l Noé
242 ncruz
243 099586197 B i o l o gı́ a Hubard E s c a l e r a A
244 lfredo ahubard
245 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
246 elio rtapia
247 067302682 M a t e má t i c a s Viso E l i s a
248 elisa@ciencias
249
250 Acciones a r e a l i z a r :
251 [ 0 ] Terminar
252 [ 1 ] Agregar e s t u d i a n t e
253 [ 2 ] Buscar e s t u d i a n t e
254 [ 3 ] L i s t a r todos
255 [ 4 ] L i s t a r l o s que c a z a n con . . .
256 [5] Eliminar estudiante
257 [ 6 ] Mostrar e s t u d i a n t e
258 [ 7 ] A g r e g a r en o r d e n
259
260 E l i g e una o p c i ó n >>
261 0
262 Mucho g u s t o en h a b e r l e s e r v i d o
263 Fue un p l a c e r s e r v i r t e
264 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
5.2 La lista de registros 264

En la ejecución de la clase MenuCursoLista podemos observar que la lista inicial


se arma a pie –lı́neas 25600 a 27300 del código–, en orden alfabético. Dentro de la
ejecución de daMenu, se agrega en orden a Viso Elisa –lı́neas 13 a 33–, por lo que
la lista sigue ordenada, como se muestra en las lı́neas 48 a 58 de la ejecución.
A continuación se elige agregar al principio un registro –69 a 89–, que de todos
modos le toca al principio de la lista, quedando la lista como se muestra en las
lı́neas 103 a 116. Inmediatamente se agrega otro estudiante al principio de la lista
-lı́neas 128 a 147–, pero como el apellido empieza con Tapia, la lista se desarregla
en cuanto al orden alfabético, como se observa cuando, a continuación de agregar
el registro, se pide la lista completa en las lı́neas 159 a 176.
Se solicita, asimismo, que se elimine a un estudiante cuyo nombre contenga la
subcadena Garcı́a –lı́neas 217 a 221– y después de corroborar que sı́ es el estudiante
deseado, lo elimina. Podemos ver que el alumno cuyo nombre tiene la subcadena
Garcı́a ya no se encuentra en la base de datos, cuando elegimos listar toda la base
en las lı́neas 233 a 249.

Con esto damos por terminado este capı́tulo. Se dejan como ejercicios las si-
guientes mejoras:

Si la lista está ordenada, al buscar a un registro se sabe que no se encontró en


cuanto se localiza el primer registro con llave mayor a la llave del que se
busca, por lo que se puede hacer la búsqueda más eficiente. Corregir el
algoritmo para que no se espere a llegar al final de la lista para saber que el
nombre no se encuentra.

Mantener la base de datos ordenada respecto al número de cuenta, no al


nombre.

Ordenar a los registros por carrera y dentro de cada carrera alfabéticamente


por nombre.

En el primer caso lo que hay que cambiar es la condición de parada del ciclo.
Para el segundo caso se trata de cambiar las condiciones de búsqueda de lugar
cuando se agrega en orden. Por último, como podemos tener varios alumnos en
la misma carrera, queremos que estén ordenados alfabéticamente dentro de cada
carrera.
265 Datos estructurados

Ejercicios

5.1.- Para el siguiente relato, describe cuáles métodos deben estar en la interfaz
para la clase:

Tenemos una clase que convierte cantidades del sistema métrico


decimal al sistema de medidas inglés y viceversa. Queremos que
contemple medidas de peso, volumen y distancia. Por ejemplo, que
convierta kilómetros a millas y viceversa; libros a kilos y viceversa;
etc.

5.2.- Queremos una clase que calcule la trayectoria balı́stica de armas de distintos
tipos –ver http://en.wikipedia.org/wiki/External ballistics. Diseña una interfaz
que calcule trayectorias balı́sticas para al menos cuatro armas distintas.
Decide si las cuatro armas requieren o no de los mismos parámetros, y si no
es ası́, diseña métodos para cada tipo de arma.
5.3.- Haz el código en Java para el algoritmo que se encuentra descrito en el
siguiente diagrama de Warnier. Se trata de un método que recibe como
parámetro de dónde leer y regresa como resultado la suma. Se le proporcio-
narán números al método y se marcará el fin de los datos con un Ctl-D, que
indica el fin de datos.
$ $
'
' &Iniciar acumulador
'
'
' Inicio
'
'
' %Pedir primer número
'
'
'
'
'
' $ $
'
'
' '
' &suma Ð suma + cuadrado(número)
'
&Suma número '
' '
'
'
'
Suma de &Pudo leer ' %Leer el siguiente número
cuadrados '
' (mientras no
'
'
' se acabe ''
'
'
'
'
la entrada) '
'
'
'
` !
'
' '
%
'
'
'
Pudo leer H
'
'
' !
'
%Final Entregar suma
Nota: Deberá codificarse un método en Java.
5.4.- Tenemos la clase CursoListas como se construyó en este capı́tulo. Suponga-
mos que queremos a los estudiantes inscritos en carreras del área de Fı́sico-
Matemáticas (claves cien). Dado el siguiente algoritmo (en un diagrama de
5. Ejercicios 266

Warnier), extrae de la base de datos a todos los estudiantes inscritos en el


área de Ciencias Fı́sico-Matemáticas. Puedes suponer que el método se en-
cuentra en la clase CursoListas y que tiene acceso a todos los métodos de la
misma.
$ $
'
'
' &Colocarse al principio
'
'
' Inicio de la lista
'
'
' %
'
'
'
Inicializar cadena
'
'
'
'
'
' $ "
'
'
' '
' Registro actual Agrégalo al
& '
'
'
Extrae estu- '
& en AFM listado
diantes de AFM '
' Procesa registro `
'
'
' (mientras haya) ' #
'
' '
'
'
'
'
'
'
' %Registro actual H
'
'
'
'
' en AFM
'
'
'
'
'
' !
%Final Entregar listado

5.5.- En el diagrama de Warnier que sigue se encuentra un algoritmo para cambiar


el número de cuenta de un estudiante. Si suponemos que la lista está orde-
nada alfabéticamente por nombre del estudiante, codifica en Java, dentro de
la clase CursoListas, un método que cambie el número de cuenta de un estu-
diante. El método recibe la referencia del estudiante al que se desea cambiar
y el nuevo número de cuenta y regresa el número de cuenta anterior.

$ !
'
'
' Inicio Obtener nuevo número de cuenta
'
'
'
'
'
'
'
& #
Pedir viejo número de cuenta
Cambia cuenta Proceso
'
'
' Cambia el número de cuenta
'
'
'
'
'
' !
'
%Final Regresa el viejo número de cuenta

5.6.- Como en el caso anterior, tenemos un curso registrado con los nombres
ordenados alfabéticamente por nombre del estudiante. Queremos cambiar el
nombre y, si es necesario, reubicar el registro. El algoritmo se encuentra en el
diagrama de Warnier que sigue. Codifica el algoritmo a un método en Java,
267 Datos estructurados

en la clase CursoListas, que reciba como parámetros la referencia al registro


a modificar y el nuevo nombre a poner. El método no regresa nada.
$ !
'
'
' Inicio Construir nuevo registro
'
'
'
'
'
' $
'
'
' '
'
'
& '
'
&Elimina registro anterior
Cambia nombre Proceso
'
'
' '
'
'
'
'
' %Agrega en orden registro nuevo
'
'
'
'
'
'
'
' !
% Final H
5.7.- Haz un método para la clase CursoListas que imprima los registros en posi-
ción par de la lista (0, 2, 4, . . . ). El algoritmo se muestra en el diagrama de
Warnier que se encuentra a continuación.
$ $
'
'
' '
'
'
' '
'
'
Colocarse al principio
'
'
' '
& de la lista
'
'
' Iniciar acumulador
'
'
'
Inicio
'
'
' '
'
'
del listado
'
'
' '
'
'
' %Iniciar contador
'
'
'
de posiciones
'
'
'
'
' $ $
'
'
' '
' '
'
'
' '
' &Agrégalo a la lista
& '
'
' Registro en Incrementa contador
Imprimir registros '
'
' posición par '
'
en posiciones par '
' '
' %Pasa al siguiente
'
'
' &
'
'
'
Procesa registro
`
'
' (mientras haya) '' $
'
'
' '
'
' '
'
'
' '
' &
'
'
' '
'
' Registro en H
'
' '
' '
'
'
' % posición par '
%
'
'
'
'
'
'
'
'
' "
'
'
%Final Regresa listado
armado

5.8.- Tenemos una lista de enteros cuyos métodos básicos de acceso a sus atributos
se dan en el listado que sigue.
5. Ejercicios 268

package C o n s u l t a s L i s t a s ;
import j a v a . u t i l . S c a n n e r ;

public class Enteros {


p r i v a t e i n t num ;
private Enteros sigue ;
public Enteros () {
}
public Enteros ( int k) {
num = k ;
}
p u b l i c i n t getNum ( ) {
r e t u r n num ;
}
p u b l i c v o i d setNum ( i n t num) {
t h i s . num = num ;
}
public Enteros getSigue () {
return s i g u e ;
}
public void s e t S i g u e ( Enteros s i g u e ) {
this . sigue = sigue ;
}
}

Agrega el método main y construye a pie una lista que tenga los números
(5,2,0,7,10,3,4). La forma de construir esta lista se muestra en el diagrama
de Warnier a continuación.
$ $
'
'
' & Declarar cabeza
'
'
' inicio con número 5
'
'
' %
'
'
' actual Ð cabeza
'
'
'
'
& $
Construir lista '
&Construir nuevo con num
'
'
'
Agregar num
actual.sigue Ð nuevo
'
'
'
(num=2,0,7,10,3,4) '
%
'
'
' actual Ð nuevo
'
'
'
'
'
' !
'
%Final H
Cabe aclarar que no se puede hacer esto en una iteración, pero podemos
usar nuevo para ir apuntando al elemento que se acaba de agregar.

5.9.- Escribe un método en esta clase que liste a todos los elementos de una lista.
269 Datos estructurados

El algoritmo es el usual para recorrer listas. Aplı́calo a la lista que acabas


de crear en el ejercicio anterior.

5.10.- Haz un método ingrese a un objeto de la clase Enteros en orden a una lista
cualquiera.

5.11.- Haz un método que tome una lista cualquiera de Enteros y elemento por
elemento lo clone y construya una lista ordenada con ellos. Clonar un objeto
quiere decir construir uno nuevo con los mismos valores. La lista a la que
se clona debe permanecer igual. El algoritmo se muestra en el diagrama de
Warnier que se encuentra a continuación. El método recibe como parámetro
la lista a clonar y regresa la lista clonada ordenada.
$ $ !
'
'
' '
' lista vacı́a ListaClonada Ð null
'
' '
'
'
'
'
' '
' ` $
'
' &
'
'
' '
'
'
Inicio
' '
&Clonar primer elemento
'
' '
'
'
'
'
' '
' lista vacı́a Agregarlo a ListaClonada
'
' '
% '
'
%
'
'
'
'
actual Ð segundo de ListaVieja
'
'
&
Clonar y ordenar $
ListaVieja ' '
'
' Clonar el elemento actual
' '
' '
'
' Procesa elemento '
en ListaVieja
'
' &
'
'
' de ListaVieja Agregarlo en orden
'
'
' (mientras haya) '' a ListaClonada
'
' '
'
'
'
'
' '
% Avanzar al siguiente
'
'
' elemento en ListaVieja
'
'
'
'
'
' !
'
%Final Regresa ListaClonada

5.12.- Haz un diagrama de Warnier para determinar, en una lista ordenada de


enteros (como la anterior) cuál es el número que más veces se repite. Aspectos
que debe considerar el algoritmo:

Debe registrar el máximo número de duplicados. Al empezar este núme-


ro es 1.
Recorre la lista comparando el número actual con el anterior para ver
si son iguales. Si los son, incrementa un contador; si no lo son y los que
acaba de ver son más, sustituye el número que era el más repetido y la
cuenta de más repetido.
5. Ejercicios 270

5.13.- Haz un diagrama de Warnier que encuentre el número mayor en una lista
de Enteros.

5.14.- Haz un diagrama de Warnier que encuentre el número menor en una lista
de Enteros.

5.15.- Haz un diagrama de Warnier para, dados dos enteros, el algoritmo reco-
rra una lista de Enteros y sustituya las presencias del primer entero por el
segundo entero.

5.16.- Diseña una clase que tenga objetos que puedan pertenecer a una lista, con
un campo que pueda almacenar un entero y con dos referencias, una de ellas
al objeto que sigue y otra al que le precede.

5.17.- Dada una lista de elementos como los descritos en el ejercicio anterior,
describe el algoritmo (en un diagrama de Warnier) para dar de alta a un
entero en una lista ordenada –se deben actualizar ambas referencias en cada
inserción a la lista–.

5.18.- Da el algoritmo para eliminar a un elemento dado de la lista –nuevamente


tener cuidado con las dos referencias–.
Herencia
6
En los lenguajes orientados a objetos, la herencia, que conlleva la capacidad
de reutilizar código de manera inteligente, es una de las caracterı́sticas más im-
portantes de este enfoque.

6.1 Extensión de clases

Una de las caracterı́sticas más valiosas en el código que hacemos es la posibili-


dad de reutilizarlo, esto es, tomar un código que ya trabaja para cierta aplicación
y usarlo en otra aplicación parecida pero no idéntica. En el contexto de lenguajes
tradicionales esto se logra copiando aquellas partes del programa que nos sirven
y modificándolas para que se ajusten a nuevas situaciones. Estos mecanismos son
en el mejor de los casos muy laboriosos y en el peor susceptibles a errores.
Uno de los mayores beneficios de la Orientación a Objetos es la posibilidad de
extender clases ya construidas – construir subclases – de tal manera que se pueda
seguir utilizando la clase original en el contexto original, pero se pueda, asimismo,
agregarle atributos a la subclase, redefinir algunos de los métodos para que tomen
en cuenta a los nuevos atributos o agregar métodos nuevos.
La clase original es la superclase con respecto a la nueva; decimos que la
subclase hereda los atributos y métodos de la superclase. Que herede quiere decir
6.2 Arreglos 272

que, por el hecho de extender a la superclase, tiene al menos todos los atributos
y métodos de la superclase. Antes de entrar de lleno al concepto de herencia,
revisaremos a una clase de objetos muy común en las ciencias de la computación
que son los arreglos, estructuras de datos lineales, homogéneas, estáticas y de
acceso directo. Los necesitamos para modificar un poco a la superclase en lo que
respecta a las variables estáticas que están formadas por cadenas.

6.2 Arreglos

En esta sección estudiaremos estructuras repetitivas de datos, organizados co-


mo vectores, matrices, etc.

6.2.1. Arreglos de una dimensión

Hemos tratado los nombres que corresponden a las carreras como una cadena,
pero en la clase para el catálogo de carreras están implementados en arreglos.
Tenemos una lista con los nombres de las carreras y una lista “paralela” con las
claves de las carreras. El nombre que corresponde a la clave en la posición i de
la lista de claves es el que se encuentra en la posición i en la lista de nombres.
Organizamos estas listas colocando a cada nombre de carrera en una posición, y
en otro arreglo, en la posición correspondiente, colocamos su clave. Es conveniente
tener ordenadas las claves pues facilita –como verán más adelante– localizar una
clave determinada.
Si queremos registrar las calificaciones de las tareas entregadas por un estu-
diante durante el semestre, sabemos que vamos a dejar el mismo número de tareas
y que éste queda fijo al inicio del semestre (a lo más, una por semana, o sea 16).
Podemos pensar en las calificaciones de cada estudiante como un vector,

calif0 , calif1 , . . . , calif15

donde estamos suponiendo que cada estudiante tiene lugar para, a lo más, 16
calificaciones (del 0 al 15). Se ve, asimismo, muy útil el poder manejar a cada una
de las calificaciones refiriéndonos a su subı́ndice, en lugar de declarar calif0, calif1,
etc., imitando el manejo que damos a los vectores en matemáticas.
273 Herencia

Es importante notar que lo que tenemos es una colección de datos del mismo
tipo que se distinguen uno de otro por el lugar que ocupan dentro de la estructura.
A estas colecciones les llamamos en programación arreglos. La declaración de
arreglos tiene la siguiente sintaxis:

Sintaxis:
xdeclaración de arregloy ::= xtipoy[ ] xidentificadory;
Semántica:
Se declara una variable que va a ser una referencia a un arreglo de objetos
o datos primitivos del tipo dado.

Veamos algunos ejemplos:

int [ ] arregloDeEnteros ; // Arreglo de enteros


EstudianteBasico [ ] estudiantes ; // Arreglo de objetos del tipo
// EstudianteBasico.
float [ ] vector ; // Arreglo de reales.
String [ ] cadenas; // Arreglo de cadenas

Noten como en la declaración no estamos diciendo el tamaño del arreglo. Esto


es porque en este momento Java no tiene por qué saber el número de elementos,
ya que lo único que está reservando es una variable donde guardar la referencia
de donde quedará, en el heap, el arreglo.
Podemos en la declaración de los arreglos determinar el tamaño que van a
tener. Lo hacemos como en las cadenas, inicializando el arreglo en el momento de
declararlo, proporcionando una lista de los elementos que queremos en el arreglo.
En ese caso, el tamaño del arreglo quedará fijado al número de elementos que se
dé en la lista. La sintaxis para este tipo de declaración es:

Sintaxis:
xdeclaración de arreglos con inicializacióny ::=
xtipoy[ ] xidentificadory = { xlistay };
Semántica:
Para inicializar el arreglo se dan, entre llaves, valores del tipo del arre-
glo, separados entre sı́ por coma. El arreglo tendrá tantos elementos como
aparezcan en la lista, y cada elemento estará creado de acuerdo a la lista.
En el caso de un arreglo de objetos, la lista deberá contener objetos que ya
existen, o la creación de nuevos mediante el operador new.
6.2 Arreglos 274

Extendamos los ejemplos que acabamos de dar a que se inicialicen en el mo-


mento de la declaración:
Arreglo de enteros con cinco elementos, todos ellos enteros. Corresponden a
los primeros cinco números primos.

int [ ] primos = {2,3,5,7,11};

Arreglo de reales con tres elementos.

float [ ] vector = { 3.14, 8.7, 19.0 };

Arreglo de dos cadenas, la primera de ellas con el valor “Sı́” y la segunda


con el valor “No”.

String [ ] cadenas = { "Sı́", "No"};

Arreglo de objetos del tipo Estudiante con tres elementos, el primero y tercero
con el constructor por omisión y el segundo con los datos que se indican.
E s t u d i a n t e paco = new E s t u d i a n t e ( "Paco" ,
" 095376383 " , "Compu" , 7 ) ;

E s t u d i a n t e B a s i c o [ ] e s t u d i a n t e s = {new E s t u d i a n t e ( ) ,
paco ,
new E s t u d i a n t e ( )
};
Agregamos la declaración de los nombres de las carreras (String[]) y de las
claves de las mismas (int[]). Podemos ver los dos arreglos como paralelos:
al número de clave en la posición i le corresponde el nombre en la misma
posición.
static final String [ ] sCarreras = {
"Código invalido " , // lugar 0
" Actuarı́a " , // lugar 1
" Ciencias de la Computación " , // lugar 2
"Fı́sica" , // lugar 3
" Matemáticas " , // lugar 4
" Ciencias de la Tierra" , // lugar 5
" Biologı́a " , // lugar 6
"Manejo Sustentable de Zonas Costeras " } ; // lugar 7
275 Herencia

Y para las claves de las carreras hacemos un arreglo “paralelo” que contenga
las claves correspondientes:
static final int [ ] carreras = {
0, // l u g a r 0
101 , // l u g a r 1
104 , // l u g a r 2
106 , // l u g a r 3
122 , // l u g a r 4
127 , // l u g a r 5
201 , // l u g a r 6
217}; // l u g a r 7

Veamos en las figuras 6.1 a 6.3 a continuación cómo se crea el espacio para los
arreglos que se mostraron arriba. En los esquemas marcamos las localidades de
memoria que contienen una referencia con una “@”en la esquina superior izquier-
da. Esto quiere decir que su contenido es una dirección en el heap. Las localidades
están identificadas con rectángulos. El número que se encuentra ya sea inme-
diatamente a la izquierda o encima del rectángulo corresponde a lo que serı́a la
dirección de memoria en el heap (Java nunca maneja directamente direcciones de
memoria).

Figura 6.1 int[ ] primos = {2,3,5,7,11};

Variables Heap
1000 1004 1008 1012 1016
@ 1000 2 3 5 7 11
primos [0] [1] [2] [3] [4]

Figura 6.2 float [ ] vector = { 3.14, 8.7, 19.0};

Variables Heap
2124 2130 2136
@ 2124 3.14 8.7 19.0
vector [0] [1] [2]
6.2 Arreglos 276

Figura 6.3 String [ ] cadenas = { ”Sı́”, ”No”};

Variables Heap
3826
@ “Si”
@ 2784 3826 [0]
2784 @ 4218
2790 4218 [1]
cadenas “No”

Figura 6.4 Declaración con definición de un arreglo de objetos

E s t u d i a n t e [ ] e s t u d i a n t e s = { new E s t u d i a n t e ( ) ,
new E s t u d i a n t e ( "Paco" , " 095376383 " , c l a v e P a c o , 7 ) ,
new E s t u d i a n t e ( ) } ;

Variables Heap nombre cuenta carrera


@ @
null null 0
1500 1506 1512
@ 1020 @
1500 [0]
1020
@ nombre cuenta carrera
estudiantes 1026 2054 [1] @ @
@
3200 4100 7
1032 1820 [2] 2054 2060 2066

“Paco”
3200

“095376383”
4100

nombre cuenta carrera


@ @
null null 0
1820 1826 1832

Figura 6.5 Declaración e inicialización de las claves de carreras


int[] claves = {0, 101, 104, 106, 122, 127, 201, 217};
Variables Heap
1000 1004 1008 1012 1016 1020 1024 1028
@ 1000 0 101 104 106 122 127 201 217
claves [0] [1] [2] [3] [4] [5] [6] [7]
277 Herencia

Figura 6.6 Declaración con definición del vector de nombres de carreras

S t r i n g [ ] s C a r r e r a s = {"Código inválido " ,


" Actuarı́a " ,
" Ciencias de la Computación " ,
"Fı́sica" ,
" Matemáticas " ,
" Ciencias de la Tierra" ,
" Biologı́a " ,
"Manejo Sustentable de Zonas Costeras " } ;

Variables Heap 3000


“Código inválido”
@ @ 4002
1000 1000 3000 [0]
@ “Actuarı́a”
sCarreras 1008 4002 [1] 2184
@ “Ciencias de la Computación”
1016 2184 [2]
@ 3226
1024 3226 [3]
@ “Fı́sica”
1032 4100 [4] 4100
@
1040 2828 [5] “Matemáticas”
@ 2828
1048 2500 [6]
@ “Ciencias de la Tierra”
1048 2320 [7] 2500
“Biologı́a”
2320
“Manejo Sustentable de Zonas Costeras”

La manera “normal” de darle tamaño a un arreglo es creándolo con el enun-


ciado new y especificando el número de elementos que va a tener el arreglo. En
este caso las referencias a objetos se inicializan en null mientras que los datos
numéricos se inicializan en 0 (cero). La sintaxis precisa se da a continuación.

Sintaxis:
xdeclaración de un arreglo con tamaño dadoy ::=
xtipoy xidentificadory = new xtipoy[xexpresión enteray];
Semántica:
Se inicializa la referencia a un arreglo de referencias en el caso de objetos,
o de datos en el caso de tipos primitivos.
6.2 Arreglos 278

Si los ejemplos que dimos antes los hubiéramos hecho sin la inicialización serı́an:

1 i n t [ ] p r i m o s = new i n t [ 5 ] ;
2 f l o a t [ ] v e c t o r = new f l o a t [ 3 ] ;
3 E s t u d i a n t e B a s i c o [ ] e s t u d i a n t e s = new E s t u d i a n t e B a s i c o [ 3 ] ;
4 S t r i n g [ ] c a d e n a s = new S t r i n g [ 2 ] ;
5 S t r i n g [ ] s C a r r e r a s = new S t r i n g [ 8 ] ;
6 i n t [ ] c l a v e s = new i n t [ 8 ] ;

y los esquemas de su organización en memoria quedarı́a como se muestra en las


figuras 6.7 a 6.10.
La creación de los arreglos – en la modalidad que estamos presentando – no
tiene por qué darse en la declaración, sino que, como con cualquier otro dato,
ya sea éste primitivo u objeto, se puede hacer como un enunciado de asignación
común y corriente. Lo único que hay que tener en mente es que a una misma
referencia se le pueden asignar distintos espacios en el heap.

Figura 6.7 int [ ] primos = new int[5];

Variables Heap
1000 1004 1008 1012 1016
@ 1000 0 0 0 0 0
primos [0] [1] [2] [3] [4]

Figura 6.8 float [ ] vector = new float[3];

Variables Heap
2124 2130 2136
@ 2124 0.0 0.0 0.0
vector [0] [1] [2]
279 Herencia

Figura 6.9 EstudianteBasico [ ] estudiantes = new EstudianteBasico[3];

Variables Heap
@ 1020 @
null [0]
1020
estudiantes @
1026 null [1]
@
1032 null [2]

Figura 6.10 String [ ] cadenas = new String[2];

Variables Heap

@
@ 2784 null [0]
2784 @
2790 null [1]
cadenas

Figura 6.11 Declaración del vector de nombres de carreras

String[ ] sCarreras = new String[8];

Variables Heap
[0] [1] [2] [3] [4] [5] [6] [7]
@ 1000 @null @null @null @null @null @null @null @null

sCarreras 1000 1008 1016 1024 1032 1040 1048 1048

Para las claves de las carreras, podrı́amos tener la declaración sin inicialización
que se muestra en la figura 6.12.
6.2 Arreglos 280

Figura 6.12 Declaración del vector con claves de carreras

i n t [ ] c l a v e s = new i n t [ 8 ] ;
Variables Heap
1000 1004 1008 1012 1016 1020 1024 1028
@ 1000 0 0 0 0 0 0 0 0
claves [0] [1] [2] [3] [4] [5] [6] [7]

Como ya mencionamos, en el caso de los elementos numéricos de un arreglo,


éstos se inicializan en 0.

Uso de los elementos de un arreglo


En el caso de los arreglos de una dimensión, para usar a un elemento del arreglo
debemos seleccionarlo, de manera similar a como seleccionamos a un atributo o
método de un objeto; pero mientras que en el caso de los objetos se utiliza el
operador punto (.), en el caso de los arreglos de una dimensión se usa el operador
r s, cuya sintaxis es:

Sintaxis:
xselección de un elemento de un arregloy ::=
xid. de arregloy [ xexpresión enteray ]
Semántica:
El operador r s es el de mayor precedencia de entre los operadores de Ja-
va. Eso indica que evaluará la expresión dentro de ella antes que cualquier
otra operación (en la ausencia de paréntesis). Una vez obtenido el entero
correspondiente a la xexpresión enteray –que pudiera ser una constante en-
tera– procederá a elegir al elemento en el arreglo con ese ı́ndice. El resultado
de esta operación es del tipo de los elementos del arreglo. De no existir el
elemento al que corresponde el ı́ndice calculado, el programa abortará con
el mensaje ArrayIndexOutOfBoundsException. Al primer elemento del arre-
glo le corresponde siempre el ı́ndice 0 (cero) y al último elemento el ı́ndice
n  1, donde el arreglo se creó con n elementos.

Es importante apreciar que el tamaño de un arreglo no forma parte del tipo.


Lo que forma parte del tipo es el número de dimensiones (vector (1), matriz (2),
281 Herencia

cubo (3), . . . ) y el tipo de sus elementos (int, float, Estudiante, . . . ).

Los arreglos son estructuras de datos estáticas


Cuando decimos que un arreglo no puede cambiar de tamaño una vez creado
nos estamos refiriendo al espacio asignado en el heap. Sin embargo, es perfecta-
mente válido que una misma referencia apunte a un arreglo de un cierto tamaño,
para pasar después a apuntar a uno de otro tamaño. Supongamos, por ejemplo,
que tenemos la siguiente secuencia de enunciados:

1 int [ ] enteros ;
2 ...
3 e n t e r o s = new i n t [ 5 ] ;
4 int i = 0;
5 while ( i < 5) {
6 e n t e r o s [ i ] = ( i + 1) ∗ 2 ;
7 i ++;
8 }
9 e n t e r o s = new i n t [ 7 ] ;

La secuencia de pasos que se dan durante la ejecución se puede observar en


la figura 6.13 en la siguiente página. El número entre paréntesis a la izquierda
de cada sub-esquema corresponde al número de instrucción que se terminó de
ejecutar.
Como se puede observar en esta figura, el arreglo que se crea en la lı́nea 3
del código no es el mismo que el que se crea en la lı́nea 9: no se encuentran en
la misma dirección del heap y no contienen lo mismo. Insistimos: no es que haya
cambiado el tamaño del arreglo –conservando el contenido–, sino que se creó un
arreglo nuevo; se perdió el acceso al anterior, por lo que se convierte en basura.

6.2.2. Iteración enumerativa

En el código que acabamos de presentar utilizamos una iteración que no


habı́amos visto (pero que se entiende qué hace) y que es una de las formas de
iteración más común en los lenguajes de programación. Como el tı́tulo de esta
sección lo indica, se utiliza para hacer un recorrido asociado a un tipo discreto y
que se pueda enumerar (podrı́amos utilizar también caracteres).
6.2 Arreglos 282

Figura 6.13 Reasignación de arreglos

Variables Heap
p1q
@ null
enteros

Variables Heap
p3q [0] [1] [2] [3] [4]

@
0 0 0 0 0
1260 1260 1264 1268 1272 1276
enteros

Variables Heap
p8q [0] [1] [2] [3] [4]

@
2 4 6 8 10
1260 1260 1264 1268 1272 1276
enteros

Variables Heap
p9q [0] [1] [2] [3] [4]

@
2 4 6 8 10
2580 1260 1264 1268 1272 1276
enteros
[0] [1] [2] [3] [4] [5] [6]
0 0 0 0 0 0 0
2580 2584 2588 2592 2596 2600 2604

La sintaxis general del enunciado for se da en el cuadro de la figura 6.14 en la


página opuesta.
Vimos ya un ejemplo sencillo del uso de un while para recorrer un arreglo uni-
dimensional. Sin embargo, para este tipo de tareas el for es el enunciado indicado.
La misma iteración quedarı́a como se muestra en el listado 6.1.
Código 6.1 Llenado de un arreglo usando el for
1 int [ ] enteros ;
2 ...
3 e n t e r o s = new i n t [ 5 ] ;
4 f o r ( i n t i = 0 ; i < 5 ; i++ ) {
5 e n t e r o s [ i ] = ( i + 1) ∗ 2 ;
6 }
283 Herencia

Figura 6.14 Sintaxis del enunciado for


Sintaxis:
xenunciado de iteración enumerativay ::=
for ( xenunciado de inicializacióny ;
xexpresión booleanay ;
xlista de enunciadosy )
xenunciado simple o compuestoy
Semántica:
En la ejecución va a suceder lo siguiente:
1. Se ejecuta el xenunciado de inicializacióny.
2. Se evalúa la xexpresión booleanay.
a) Si es verdadera, se continúa en el paso 3.
b) Si es falsa, se sale de la iteración.
3. Se ejecuta el xenunciado simple o compuestoy.
4. Se ejecuta la xlista de enunciadosy.
5. Se regresa al paso 2.
Cualquiera de las tres partes puede estar vacı́a, aunque el ; sı́ tiene que
aparecer. En el caso de la primera y tercera parte, el que esté vacı́a indica
que no se hace nada ni al inicio del enunciado ni al final de cada iteración. En
el caso de una xexpresión booleanay vacı́a, se interpreta como la constante
true, o sea para siempre –o hasta que se salga de la iteración con un break–.

Hay una pequeña diferencia entre las dos versiones. En el caso del for, la
variable i es local a él mientras que en el while tuvimos que declararla fuera del
bloque. En ambos casos, sin embargo, esto se hace una única vez, que es el paso
de inicialización. En el caso del for no se puede hacer referencia a la variable i
fuera del bloque, pues no existe fuera de él, mientras que en el caso del while la
variable i existe fuera del bloque. No hay manera de usar una variable que no ha
sido declarada en la expresión booleana del while.
Otra manera de hacer esto con un for, usando dos variables enumerativas, una
para i y otra para i + 1, pudiera ser como sigue:
1 int [ ] enteros ;
2 ...
3 e n t e r o s = new i n t [ 5 ] ;
4 f o r ( i n t i = 0 , j = 1 ; i < 5 ; i ++, j ++) {
5 enteros [ i ] = j ∗ 2;
6 }
6.2 Arreglos 284

Del ejemplo anterior hay que notar que tanto i como j son variables locales al
for; para que esto suceda se requiere que la primera variable en una lista de este
estilo aparezca declarada con su tipo, y la segunda (tercera, etc.) variables deben
ser del mismo tipo; el formato debe ser de una única declaración que puede incluir
más de una variable del mismo tipo. Si alguna de las variables está declarada
antes y fuera del for aparecerá el mensaje de que se está repitiendo la declaración,
aunque esto no es exacto. Lo que sı́ es válido es tener varios enunciados for, cada
uno con una variable local i.
Por supuesto que el xenunciado simple o compuestoy puede contener o consistir
de, a su vez, algún otro enunciado for o while o lo que queramos. La ejecución va
a seguir el patrón dado arriba, terminando la ejecución de los ciclos de adentro
hacia afuera.

EJemplo 6.2.1

Supongamos que queremos calcular el factorial de un entero n que nos pasan


como parámetro. El método podrı́a estar codificado como se muestra en el códi-
go 6.2.

Código 6.2 Cálculo de n!


p u b l i c long f a c t o r i a l ( i n t n ) {
long f a c t = 1 ;
f o r ( i n t i = 2 ; i <= n ; i ++) {
fact = fact ∗ i ;
}
return f a c t ;
}

EJemplo 6.2.2

Supongamos ahora que queremos tener dos variables para controlar la itera-
ción, donde la primera nos va a decir en cuál iteración va (se incrementa de 1 en
1) y la segunda se calcula sumándose uno al doble de lo que lo que llevaba. El
encabezado del for serı́a:
285 Herencia

Código 6.3 Dos variables controlando la iteración


f o r ( i n t i =1, j =1; i <= 8 && j < 2 0 0 ; i ++, j = 2 ∗ j + 1 ) {
System . o u t . p r i n t l n ( "i=\t"
+ i
+ "\tj =\t"
+ j );
}

En este ejemplo tenemos dos inicializaciones simultáneas y dos enunciados de


iteración que se ejecutan al final del bloque, antes de regresar a volver a iterar
–se debe notar que cuando el tercer componente del for consiste de más de un
enunciado, éstos se separan entre sı́ por una coma, no un punto y coma–. Lo que
escribirı́a este segmento de programa es lo siguiente:
i= 1 j= 1
i= 2 j= 3
i= 3 j= 7
i= 4 j= 15
i= 5 j= 31
i= 6 j= 63
i= 7 j= 127
Cuando i vale 8, se deja de cumplir la expresión booleana “ i < 8 && j < 200”.
Al terminar la iteración donde escribe . . . j  127, inmediatamente después,
antes de regresar al encabezado, calcula j  j  2 1  255, que ya no cumple
la expresión booleana, por lo que ya no vuelve a entrar para i  8.
Es conveniente mencionar que en el encabezado que acabamos de ver se decla-
ran dos variables, i y j que sólo van a ser reconocidas dentro del cuerpo del for.
Regresaremos más adelante a utilizar este enunciado. Sin embargo, recomendamos
que cuando la expresión booleana no sea numérica o no tenga que ver con relacio-
nes entre enteros, mejor se use alguna de las otras iteraciones que presentamos.
Si estuviéramos en un lenguaje de programación que únicamente tuviera la
iteración while, el código serı́a como se muestra en el listado 6.4.

Código 6.4 Codificación de iteración for con while


int i = 1 ,
j = 1; /∗ I n i c i a l i z a c i ó n ∗/
w h i l e ( i <= 8 && j < 2 0 0 ) { /∗ e x p r e s i ó n b o o l e a n a ∗/
System . o u t . p r i n t l n ( "i=\t" + i "\tj = " + j ) ; /∗ c u e r p o ∗/
i ++; /∗ e n u n c i a d o s de i t e r a c i ó n ∗/
j = j ∗ 2 + 1 ; /∗ e n u n c i a d o s de i t e r a c i ó n ∗/
} // w h i l e
6.2 Arreglos 286

EJemplo 6.2.3

Para el caso de las claves de carreras, si suponemos las declaraciones dadas en


las figuras 6.6 y 6.5, podemos obtener el nombre a partir de la clave. Tenemos que
buscar la clave correspondiente y usar el lugar encontrado como ı́ndice para los
nombres de las carreras. El diagrama de Warnier se encuentra a continuación:

$ $ #
'
' '
'
'
'
'
' '
' Es la buscada
Registrar el lugar
'
' Buscar clave &
'
'
' (hasta encontrarla
Salir del ciclo
'
'
' o se acaben) ' ' `
'
' '
'
' !
'
& %Es la buscada Pasar al siguiente
Encontrar
clave ' ''
'
' !
'
'
' Encontrada Mostrar el nombre de la carrera
'
'
'
'
'
'
'
` !
'
% Encontrada Mostrar que el código es inválido

La programación de la búsqueda está en el listado 6.5.


287 Herencia

Código 6.5 Localiza el nombre de una carrera dada su clave CatalogoCarreras

p r i v a t e s t a t i c S t r i n g [ ] s C a r r e r a s = {" Código inválido " ,


" Actuarı́a " ,
" Ciencias de la Computación " ,
" Fı́sica " ,
" Matemáticas " ,
" Ciencias de la Tierra " ,
" Biologı́a " ,
" Manejo Sustentable de Zonas Costeras " } ;
p r i va t e s t a t i c i n t [ ] c l a v e s = {0 , 101 , 104 , 106 ,
122 , 127 , 201 , 217};

public S t r i n g daCarrera ( i n t buscada ) {


int i = 0;
f o r ( i = 0 ; i < c l a v e s . l e n g t h ; i ++) {
i f ( c l a v e s [ i ] == b u s c a d a ) {
break ;
}
}
// Estamos acá con i a pu nt and o a l e n c o n t r a d o
// o mayor o i g u a l que e l tamaño d e l a r r e g l o
i f ( i < c l a v e s . l e n g t h ) { // S a l i m o s con e l b r e a k
r e t u r n " Clave : " + c l a v e s [ i ]+"\ tCarrera : "
+ sCarreras [ i ] . trim ( ) ;
} else {
r e t u r n " Clave : "+b u s c a d a+"\ tCarrera : "
+ sCarreras [ 0 ] . trim ( ) ;
}
}
6.2 Arreglos 288

EJemplo 6.2.4
Podemos tener iteraciones anidadas, como lo dice la sintaxis del enunciado.
Por ejemplo, queremos producir un triángulo con la siguiente forma:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
En este caso debemos recorrer renglón por renglón, y en cada renglón reco-
rrer tantas columnas como renglones llevamos en ese momento. El código para
conseguir escribir esto se encuentra en el listado 6.6.

Código 6.6 Construcción de un triángulo de números


f o r ( i n t i = 1 ; i <= 9 ; i ++) { /∗ r e c o r r e l o s r e n g l o n e s ∗/
f o r ( j = 1 ; j <= i ; j ++) { /∗ r e c o r r e l a s c o l u m n a s ∗/
System . o u t . p r i n t ( j + "\t" ) ; /∗ e s c r i b e e l v a l o r de l a ∗/
/∗ columna , s i n t e r m i n a r ∗/
/∗ e l r e n g l ó n . ∗/
}
System . o u t . p r i n t l n ( ) ; /∗ cambia de r e n g l ó n ∗/
}

Conforme resolvamos más problemas utilizaremos todo tipo de iteraciones y


aprenderemos a elegir la más apropiada para cada caso.

6.2.3. Arreglos de más de una dimensión


Es obvio que podemos necesitar arreglos de más de una dimensión, como son las
matrices, arreglos de dos dimensiones. Para Java los arreglos de dos dimensiones
son arreglos de arreglos, los de tres dimensiones son arreglos de arreglos de arreglos
y ası́ sucesivamente. La declaración se hace poniendo tantas parejas de corchetes
como dimensiones queramos en un arreglo, como se puede ver a continuación.
289 Herencia

int [ ] [ ] matriz ; // 2 d i m e n s i o n e s : m a t r i z
EstudianteBasico [ ] [ ] [ ] f a c u l t a d ; // 3 d i m e n s i o n e s : cubo

Por la manera en que maneja Java los arreglos, si quiero inicializar un arreglo
en el momento de declararlo, digamos de dos dimensiones, tendré que darle entre
llaves las inicializaciones para cada uno de los renglones:

Renglones: [0] [1] [2]


hkkkkkikkkkkj hkkkikkkj hkkikkj
int [ ] [ ] matriz = {{2 ,2 ,3 ,3} ,{3 ,4 ,5} ,{8}};

Reconocemos inmediatamente tres sublistas encerradas entre llaves, lo que


indica que el arreglo matriz tiene tres renglones. Cada uno de los renglones tiene
tamaño distinto, y esto es válido, pues siendo un arreglo de arreglos, cada uno de
los arreglos en la última dimensión puede tener el número de elementos que se
desee. Un esquema de cómo quedarı́an en memoria se puede ver en la figura 6.15.
Figura 6.15 Acomodo en memoria de un arreglo de dos dimensiones

int[ ][ ] matriz = {{2,2,3,3},{3,4,5},{8}};


[0]
Variables Heap
8
[0] [1] [2] 8154
@ @ @ @
1000 3260 4210 8154 [0] [1] [2]
matriz 1000 1008 1016
3 4 5
4210 4214 4218

[0] [1] [2] [3]


2 2 3 3
3260 3264 3268 3272

Podemos, para un arreglo de 3 dimensiones, tener la siguiente sucesión de


enunciados:

1 i n t [ ] [ ] [ ] cubos ;
2 c u b o s = new i n t [ 3 ] [ ] [ ] ;
3 c u b o s [ 0 ] = new i n t [ 6 ] [ ] ;
4 c u b o s [ 1 ] = new i n t [ 3 ] [ 3 ] ;
5 c u b o s [ 2 ] = new i n t [ 2 ] [ ] ;
6.2 Arreglos 290

Esta declaración y definiciones, al terminar de ejecutarse la lı́nea 5, queda en


memoria como se muestra en la figura 6.16.
Figura 6.16 Almacenado en memoria del arreglo cubos

Variables Heap [0] [1] [2] [3] [4] [5]


@ @ @ @ @ @
(null) (null) (null) (null) (null) (null)
@ @ [0] 8600 8604 8608 8612 8616 8620
3024 (4820) 4820 (8600)
@ [1] [0]
4824 (9300) [0] [1] [2] 0 2740 [0]
@ [2] @ @ @ [1] 0 7200
4828 (2456) (5700) (7200) (2740) 0 2744 [1]
9300 9304 9308 [2] 0 7204
0 2748 [2]
0 7208
[0]
0 5700
[0] [1] [1]
@ @ 0 5704
(null) (null) [2]
2456 2460 0 5708

Con la declaración que acabamos de ver tendrı́amos un arreglo de tres dimen-


siones; en la primera dimensión tenemos tres elementos: el primer elemento es un
arreglo de 6 arreglos, aunque no sabemos todavı́a de qué tamaño es cada uno de
los 6 arreglos; el segundo elemento es un arreglo de tres arreglos de tres elementos;
el tercer elemento es un arreglo de dos arreglos y no sabemos cuantos elementos
en cada arreglo.
Podemos observar en esta figura que únicamente la última dimensión –en este
caso la tercera– corresponde a variables, inicializadas con el valor 0 para enteros.
Estamos suponiendo que cada referencia ocupa 4 bytes, lo mismo que cada entero,
aunque esto puede no ser preciso. Lo que se desea enfatizar es cómo se almacenan
contiguos los elementos de cada dimensión, registrando direcciones en el heap que
son contiguas –9300, 9304, 9308 por ejemplo–.
Como podemos tener también variables o expresiones enteras que nos den el
tamaño de un arreglo, es importante que en cualquier momento podamos saber
el tamaño de alguna de las dimensiones del mismo. Para eso, la clase que co-
rresponde a los arreglos tiene una variable length que nos regresa el tamaño del
arreglo. Por ejemplo, para los enunciados que acabamos de hacer, cubos[0].length
es 6, cubos[1][1].length es 3 y cubos[2].length es 2. A diferencia de las cadenas
–String– length es un atributo, por lo que no va seguido de paréntesis como en las
cadenas, donde corresponde a un método.
Resumiendo, los arreglos en Java tienen las siguientes caracterı́sticas:
291 Herencia

(i.) Un arreglo es un objeto. Esto quiere decir que cuando declaramos un arreglo
simplemente estamos reservando espacio en memoria para la referencia, la
dirección en el heap donde se encontrarán los elementos del arreglo.
(ii.) Es una estructura de datos estática: una vez dado su tamaño, el arreglo no
puede achicarse o agrandarse.
(iii.) Podemos tener arreglos del número de dimensiones y del tipo que queramos.
(iv.) Los arreglos son estructuras de datos homogéneas, porque todos sus elemen-
tos son del mismo tipo.
(v.) Los arreglos de dos dimensiones no tienen porqué tener el mismo número
de elementos en cada renglón del arreglo. Esto se extiende en todas las
dimensiones que tenga el arreglo.

Uso de arreglos de dos o más dimensiones


De manera similar a como lo hacemos en arreglos de una dimensión, podemos
seleccionar a un elemento de un arreglo de más de una dimensión dando una
expresión entera para cada una de las dimensiones. Los elementos se van eligiendo
de izquierda a derecha, en el mismo orden en que fueron creados. No hay que
perder de vista que en el caso de Java una matriz es un arreglo de arreglos, y
por la manera en que los representa internamente se puede dar un error de ı́ndice
en cualquiera de las dimensiones, por lo que es importante mantener presente
la construcción del arreglo cuando se seleccionan elementos dentro de él. Las
primeras k  1 dimensiones, si se selecciona a un elemento de ellos, obtendremos
una referencia a arreglos; es únicamente en la última dimensión (la del extremo
derecho) donde obtendremos ya un elemento del tipo dado para el arreglo.

6.2.4. Arreglos como parámetros o valor de regreso de un método

En algunos otros lenguajes el paso de arreglos como parámetros es realmen-


te complicado. Afortunadamente, por el manejo de arreglos que tiene Java (de
guardar las referencias nada más) este aspecto es muy sencillo en este lenguaje.
Para pasar un arreglo como parámetro únicamente hay que indicarle a Java el
número de dimensiones que deberá tener el argumento y el tipo de los elementos.
Esto es necesario porque tiene que saber cuántos brincos tiene que dar, a través de
las listas de referencias, para encontrar a los elementos solicitados. Por ejemplo,
en el Listado 6.7 en la siguiente página, en la lı́nea 20 se indica que se van a pasar
dos arreglos de enteros como argumentos, cada uno de ellos de dos dimensiones;
mientras que en la lı́nea 43 (del mismo listado) se indica que los argumentos serán
dos arreglos de enteros, cada uno de una dimensión. No se puede especificar el
6.2 Arreglos 292

tamaño de una dimensión en un parámetro, pues el tipo del parámetro tiene que
ver nada más con el tipo de los elementos y el número de dimensiones; lo que
hay que especificar para cada parámetro de un método es, únicamente, el tipo
del parámetro. Lo mismo sucede con el valor que regresa un método: únicamente
hay que especificar el tipo del valor que regresa el método, que en el caso de los
arreglos, repetimos una vez más, consiste del tipo de los elementos y el número
de dimensiones.

Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (1/3)

1 package H e r e n c i a ;
2 import u t i l e s . Cadenas ; // Para métodos de c a d e n a s
3 /∗ ∗
4 ∗ C l a s e <code>A r r e g l o s </code >, i m p l e m e n t a v a r i o s métodos que t i e n e n
5 ∗ que v e r con a r r e g l o s .
6 ∗ En t o d o s e s t o s métodos , e l método no c o n o c e l o que l e e s t á n
7 ∗ pasando , p o r l o que t i e n e que t r a b a j a r ” i n d i r e c t a m e n t e ” .
8 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ v i s o ”> E l i s a V i s o G u r o v i c h </a>
9 ∗ @version 1.2
10 ∗/
11 public class Arreglos {
12 /∗ ∗
13 ∗ Suma d o s a r r e g l o s de d o s d i m e n s i o n e s . Los a r r e g l o s pueden
14 ∗ t e n e r d i s t i n t o número de e l e m e n t o s en c u a l q u i e r a de l a s
15 ∗ dimensiones .
16 ∗ @param A t i p o <code>i n t [ ] [ ] < / code >, p r i m e r a r r e g l o .
17 ∗ @param B t i p o <code>i n t [ ] [ ] < / code >, s e g u n d o a r r e g l o .
18 ∗ @ r e t u r n t i p o <code>i n t [ ] [ ] < / code >, un a r r e g l o con l a suma .
19 ∗/
20 p u b l i c i n t [ ] [ ] suma ( i n t [ ] [ ] A , i n t [ ] [ ] B) {
21 i n t min1 = Math . min (A . l e n g t h , B . l e n g t h ) ; // mı́nimo tamaño
22 i n t max1 = Math . max (A . l e n g t h , B . l e n g t h ) ; // máximo tamaño
23 i n t [ ] [ ] laSuma = new i n t [ max1 ] [ ] ;
24 /∗ I n v o c a m o s a q u i e n s a b e sumar a r r e g l o s de una d i m e n s i ó n ∗/
25 f o r ( i n t i =0; i < min1 ; i ++)
26 laSuma [ i ] = suma (A [ i ] , B [ i ] ) ;
27 // S i s e acabó a n t e s B
28 f o r ( i n t i = min1 ; i < max1 ; i ++) {
29 i f (A . l e n g t h > B . l e n g t h ) {
30 laSuma [ i ] = suma (A [ i ] , new i n t [ A [ i ] . l e n g t h ] ) ;
31 } else {
32 laSuma [ i ] = suma ( new i n t [ A [ i ] . l e n g t h ] , B [ i ] ) ;
33 }
34 }
35 r e t u r n laSuma ;
36 } // f i n suma ( i n t [ ] [ ] , i n t [ ] [ ] )
37
38 /∗ ∗
39 ∗ Suma d o s a r r e g l o s de una d i m e n s i ó n
293 Herencia

Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (2/3)

40 ∗ @param Dos a r r e g l o s de una d i m e n s i ó n


41 ∗ @ r e t u r n Un a r r e g l o con l a suma
42 ∗/
43 p u b l i c i n t [ ] suma ( i n t [ ] A , i n t [ ] B) {
44 i n t tMin = Math . min (A . l e n g t h , B . l e n g t h ) ;
45 i n t tMax = Math . max (A . l e n g t h , B . l e n g t h ) ;
46 i n t [ ] r e s u l t = new i n t [ tMax ] ;
47 f o r ( i n t i = 0 ; i < tMin ; i ++) // Tamaño en común
48 r e s u l t [ i ] = A[ i ] + B[ i ] ;
49 f o r ( i n t i = tMin ; i < tMax ; i ++) // l o s que s o b r a n
50 r e s u l t [ i ] = ( i < A. length )? A[ i ] : B[ i ] ;
51 i f (A . l e n g t h > B . l e n g t h ) {
52 f o r ( i n t i = tMin ; i < tMax ; i ++)
53 r e s u l t [ i ] = A[ i ] ;
54 } else {
55 f o r ( i n t i = tMin ; i < tMax ; i ++)
56 r e s u l t [ i ] = B[ i ] ;
57 }
58 return r e s u l t ;
59 } // f i n suma ( i n t [ ] , i n t [ ] )
60
61 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
62 /∗ ∗∗∗∗ Prueba de l o s métodos de l a c l a s e ∗∗∗ ∗/
63 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
64 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
65 /∗ Los d o s a r r e g l o s a sumar ∗/
66 i n t [ ] [ ] uno = { { 3 , 4 , 5 } , { 2 , 8 , 7 , 5 } , { 3 , 4 , 7 } } ;
67 i n t [ ] [ ] dos = { { 8 , 4 , 2 , 1 } , { 8 , 7 , 2 , 3 } , { 4 , 3 , 5 , 1 } } ;
68 /∗ A r r e g l o p a r a g u a r d a r l a suma ∗/
69 i n t [ ] [ ] miSuma ;
70 /∗ O b j e t o p a r a p o d e r u s a r l o s métodos ∗/
71 A r r e g l o s p r u e b i t a = new A r r e g l o s ( ) ;
72 /∗ I n v o c a c i ó n a l a suma de d o s m a t r i c e s ∗/
73 System . o u t . p r i n t l n ( "Las matrices a sumar son:" ) ;
74 System . o u t . p r i n t l n ( " Matriz uno" ) ;
75 f o r ( i n t i = 0 ; i < uno . l e n g t h ; i ++) {
76 f o r ( i n t j = 0 ; j < uno [ i ] . l e n g t h ; j ++) {
77 System . o u t . p r i n t ( Cadenas . f o r m a t o ( uno [ i ] [ j ] , ’ ’ ,4)+ "\t" ) ;
78 }
79 System . o u t . p r i n t l n ( ) ;
80 }
81 System . o u t . p r i n t l n ( " Matriz dos" ) ;
82 f o r ( i n t i = 0 ; i < d o s . l e n g t h ; i ++) {
83 f o r ( i n t j = 0 ; j < d o s [ i ] . l e n g t h ; j ++) {
84 System . o u t . p r i n t ( Cadenas . f o r m a t o ( d o s [ i ] [ j ] , ’ ’ ,4)+ "\t" ) ;
85 }
86 System . o u t . p r i n t l n ( ) ;
87 }
6.2 Arreglos 294

Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (3/3)

66 miSuma = p r u e b i t a . suma ( uno , d o s ) ;


67 /∗ I m p r e s i ó n de l o s r e s u l t a d o s ∗/
68 System . o u t . p r i n t l n ( "Suma de las dos matrices :\n"
69 + " ==================================== " ) ;
70 f o r ( i n t i =0; i <miSuma . l e n g t h ; i ++) {
71 f o r ( i n t j =0; j <miSuma [ i ] . l e n g t h ; j ++) {
72 System . o u t . p r i n t ( Cadenas . f o r m a t o ( miSuma [ i ] [ j ] , ’ ’ ,4)+ "\t" ) ;
73 } // end o f f o r ( i n t j =0; j <miSuma [ i ] . l e n g t h ; j ++)
74 System . o u t . p r i n t l n ( ) ;
75 } // end o f f o r ( i n t i =0; i <miSuma . l e n g t h ; i ++)
76 }

El método suma en la lı́nea 20 regresa un arreglo de enteros de dos dimensiones,


mientras que el método con el mismo nombre suma de la lı́nea 43 regresa un arreglo
de enteros de una dimensión. Java distingue a ambos métodos porque el tipo de
sus parámetros es distinto: uno tiene matrices mientras el otro tiene vectores,
ambas estructuras de enteros.
Cuando se pasa un arreglo como argumento (en el momento de invocar al
método) tenemos que pasar el nombre de lo que espera el método. Por ejemplo,
en el Listado 6.7 podemos ver las invocaciones a los métodos que se llaman su-
ma. La primer invocación es en la lı́nea 66 y se le pasan como argumentos los
identificadores uno y dos, que están declarados como arreglos de enteros de dos
dimensiones, lo que coincide con el tipo de los parámetros.
La otra invocación está en la lı́nea 26, dentro del método cuya firma es
suma(int[ ][ ],int[ ][ ]). En este caso le estamos pasando al método dos arreglos de
enteros, cada uno de una dimensión. Como Java maneja los arreglos a través de
referencias y vectores de referencias (como ya vimos), se le pasa simplemente una
de las referencias (de la primera dimensión) a uno de los arreglos de una dimen-
sión. Cuando en Java declaramos una matriz en realidad estamos construyendo
un arreglo en el que cada elemento es un arreglo; cuando declaramos un cubo es-
tamos declarando un arreglo en el que cada elemento es un arreglo en el que cada
elemento es un arreglo en el que cada elemento es un entero; y ası́ sucesivamente.
El resultado de la invocación de esta clase se puede ver en la Figura 6.17 en la
página opuesta.
En el listado anterior se pregunta por el tamaño de cada arreglo que se va
a manejar para no abortar el programa por ı́ndices inválidos (aborta dando el
mensaje ArrayindexOutOfBoundsException). Queda pendiente el uso de los arreglos.
¿Cuándo hay que construirlos y cuándo no? Por ejemplo, en la lı́nea 23 se construye
la primera dimensión pero la segunda no; en la lı́nea 46 también se construye el
arreglo. Esto se debe a que en el primer caso el i-ésimo renglón aparece del lado
295 Herencia

izquierdo de una asignación (lı́nea 26) y lo mismo sucede con cada uno de los
elementos en la lı́nea 48 para los elementos del arreglo result. Sin embargo, en la
lı́nea 23 no se dice cuántos elementos va a tener la segunda dimensión, porque
cuando suma regrese la referencia a un arreglo de una dimensión, esta referencia
se copiará en el lugar correspondiente (el arreglo de referencias de la primera
dimensión). Lo mismo sucede en la lı́nea 66, donde al regresar el método a un
arreglo de dos dimensiones, como lo que está regresando es una referencia, ésta
simplemente se copia.

Figura 6.17 Ejecución de la clase Arreglos

Las m a t r i c e s a sumar s o n :
M a t r i z uno
3 4 5
2 8 7 5
3 4 7
Matriz dos
8 4 2 1
8 7 2 3
4 3 5 1
Suma de l a s d o s m a t r i c e s :
====================================
11 8 7 1
10 15 9 8
7 7 12 1

Por último, para redondear el uso de arreglos, presentamos una clase que tie-
ne algunos métodos estáticos relativos a arreglos. Esta clase se encuentra en el
listado 6.8.
6.2 Arreglos 296

Código 6.8 Más trabajo con arreglos Arreglos2 (1/4)

1 package H e r e n c i a ;
2 import u t i l e s . Cadenas ;
3 /∗ ∗
4 ∗ C l a s e <code>A r r e g l o s 2 </code >, i m p l e m e n t a v a r i o s métodos que t i e n e n
5 ∗ que v e r con a r r e g l o s .
6 ∗ En t o d o s e s t o s métodos , e l método no c o n o c e l o que l e e s t á n
7 ∗ pasando , p o r l o que t i e n e que t r a b a j a r ” i n d i r e c t a m e n t e ” .
8 ∗/
9 public class Arreglos2 {
10 /∗ ∗
11 ∗ Método <code>suma</code >, suma l o s e l e m e n t o s de una m a t r i z p o r
12 ∗ columnas .
297 Herencia

Código 6.8 Más trabajo con arreglos Arreglos2 (2/4)

13 ∗ @param m a t r i z t i p o <code>i n t </code >, l a m a t r i z de d o s


14 ∗ dimensiones .
15 ∗ @ r e t u r n t i p o <code>i n t [] </ code>
16 ∗/
17 p u b l i c s t a t i c i n t [ ] suma ( i n t [ ] [ ] m a t r i z ) {
18 // A v e r i g u a r e l número máximo de c o l u m n a s
19 i n t maxCol = 0 ;
20 f o r ( i n t i =0; i < m a t r i z . l e n g t h ; i ++) {
21 i f ( maxCol < m a t r i z [ i ] . l e n g t h ) {
22 maxCol = m a t r i z [ i ] . l e n g t h ;
23 }
24 }
25 // Se c o n s t r u y e e l v e c t o r p a r a l o s r e s u l t a d o s
26 i n t [ ] r e s u l t a d o = new i n t [ maxCol ] ;
27 // s e r e c o r r e p o r r e n g l ó n y en cada columna s e a g r e g a e l
28 // c o n t e n i d o de l a columna
29 f o r ( i n t i = 0 ; i < m a t r i z . l e n g t h ; i ++) {
30 // Se r e c o r r e cada r e n g l ó n
31 f o r ( i n t j = 0 ; j < m a t r i z [ i ] . l e n g t h ; j ++) {
32 // Se r e c o r r e cada columna , d e p e n d i e n d o d e l r e n g l ó n
33 r e s u l t a d o [ j ] += m a t r i z [ i ] [ j ] ;
34 }
35 }
36 return r e s u l t a d o ;
37 }
38
39 /∗ ∗
40 ∗ Método <code>sumaPorCols </code >, r e c o r r e l a s m a t r i c e s p o r
41 ∗ columnas , v e r i f i c a n d o que e x i s t a e l e l e m e n t o .
42 ∗ @param m a t r i z t i p o <code>i n t [ ] [ ] < / code >.
43 ∗ @ r e t u r n t i p o a <code>i n t [] </ code >, l a suma p o r c o l u m n a s .
44 ∗/
45 p u b l i c s t a t i c i n t [ ] sumaPorCols ( i n t [ ] [ ] m a t r i z ) {
46 // Para d a r r e s u l t a d o , hay que s a b e r número de c o l u m n a s
47 i n t maxCol = 0 ;
48 f o r ( i n t i =0; i < m a t r i z . l e n g t h ; i ++) {
49 i f ( maxCol < m a t r i z [ i ] . l e n g t h ) {
50 maxCol = m a t r i z [ i ] . l e n g t h ;
51 }
52 }
53 // Se c o n s t r u y e e l v e c t o r p a r a l o s r e s u l t a d o s
54 i n t [ ] r e s u l t a d o = new i n t [ maxCol ] ;
55 // Se r e c o r r e p o r c o l u m n a s
56 f o r ( i n t j = 0 ; j < maxCol ; j ++) {
57 f o r ( i n t i = 0 ; i < m a t r i z . l e n g t h ; i ++) {
58 i f ( j < matriz [ i ] . length ) {
59 r e s u l t a d o [ j ] += m a t r i z [ i ] [ j ] ;
60 }
61 }
62 }
6.2 Arreglos 298

Código 6.8 Más trabajo con arreglos Arreglos2 (3/4)

63 return r e s u l t a d o ;
64 }
65
66 /∗ ∗
67 ∗ Método <code>sumaToda</code> suma a t o d o s l o s e l e m e n t o s de l a
68 ∗ m a t r i z . No s a b e c u a n t o s r e n g l o n e s hay n i e l numero de
69 ∗ e l e m e n t o s en cada columna .
70 ∗ @param m a t r i z v a l o r de t i p o <code>i n t </code> p a r a
71 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
72 ∗/
73 p u b l i c s t a t i c i n t sumaToda ( i n t [ ] [ ] m a t r i z ) {
74 i n t suma = 0 ;
75 f o r ( i n t i = 0 ; i < m a t r i z . l e n g t h ; i ++) {
76 f o r ( i n t j = 0 ; j < m a t r i z [ i ] . l e n g t h ; j ++) {
77 suma += m a t r i z [ i ] [ j ] ;
78 }
79 }
80 r e t u r n suma ;
81 }
82
83 /∗ ∗
84 ∗ Método <code>maximo</code> e n c u e n t r a e l v a l o r maximo en un
85 ∗ v e c t o r . No s a b e e l número de e l e m e n t o s en e l v e c t o r y como
86 ∗ pueden s e r t o d o s v a l o r e s n e g a t i v o s i n i c i a l i z a con e l p r i m e r
87 ∗ elemento del vector .
88 ∗ @param v e c t o r v a l o r de t i p o <code>i n t </code> p a r a
89 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
90 ∗/
91 p u b l i c s t a t i c i n t maximo ( i n t [ ] v e c t o r ) {
92 i n t maximo = v e c t o r [ 0 ] ;
93 f o r ( i n t i = 1 ; i < v e c t o r . l e n g t h ; i ++) {
94 i f ( maximo < v e c t o r [ i ] ) {
95 maximo = v e c t o r [ i ] ;
96 }
97 }
98 r e t u r n maximo ;
99 }
100 /∗ Prueba de l o s métodos ∗/
101 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
102 int [ ] [ ] laMatriz = {{2 ,3 ,1} ,{3} ,{5 ,2 ,8 ,4} ,{6 ,5 ,4 ,3 ,2 ,1} ,{3}};
103 i n t [ ] sumaCols = suma ( l a M a t r i z ) ;
104 System . o u t . p r i n t l n ( "Suma de columnas de matriz \n"
105 + "con renglones de distintos tama~ n os \n"
106 + "La matriz contiene :" ) ;
107 f o r ( i n t i = 0 ; i < l a M a t r i z . l e n g t h ; i ++) {
108 f o r ( i n t j = 0 ; j < l a M a t r i z [ i ] . l e n g t h ; j ++) {
109 System . o u t . p r i n t ( Cadenas . r e l l e n a
110 ( I n t e g e r . t o S t r i n g ( l a M a t r i z [ i ] [ j ] ) , ’i’ , ’ ’ , 4 ) +"\t" ) ;
111 }
112 System . o u t . p r i n t l n ( ) ;
299 Herencia

Código 6.8 Más trabajo con arreglos Arreglos2 (4/4)

113 }
114 System . o u t . p r i n t l n ( " =========================================\ n"
115 + "La suma es:" ) ;
116 f o r ( i n t j = 0 ; j < sumaCols . l e n g t h ; j ++) {
117 System . o u t . p r i n t
118 ( Cadenas . r e l l e n a
119 ( I n t e g e r . t o S t r i n g ( sumaCols [ j ] ) , ’i’ , ’ ’ , 4 ) + "\t" ) ;
120 }
121 System . o u t . p r i n t l n ( ) ;
122 System . o u t . p r i n t l n ( " Ahora la suma por columnas " ) ;
123 sumaCols = sumaPorCols ( l a M a t r i z ) ;
124 f o r ( i n t j = 0 ; j < sumaCols . l e n g t h ; j ++) {
125 System . o u t . p r i n t ( Cadenas . r e l l e n a
126 ( I n t e g e r . t o S t r i n g ( sumaCols [ j ] ) , ’i’ , ’ ’ , 4 ) + "\t" ) ;
127 }
128 System . o u t . p r i n t l n ( ) ;
129 System . o u t . p r i n t l n ( " ================================== "
130 + "Una matriz generada aleatoriamente :" ) ;
131 i n t [ ] [ ] numeros = new i n t [ 5 ] [ 4 ] ;
132 f o r ( i n t i = 0 ; i < 5 ; i ++) {
133 f o r ( i n t j = 0 ; j < 4 ; j ++) {
134 // A s i g n a v a l o r e s a l e a t o r i o s a numeros [ i ] [ j ]
135 numeros [ i ] [ j ] = Math . r o u n d
136 ( ( f l o a t ) ( Math . random ( ) ∗ 1 0 0 0 0 ) ) % 1 0 1 7 ;
137
138 System . o u t . p r i n t ( Cadenas . r e l l e n a ( I n t e g e r
139 . t o S t r i n g ( numeros [ i ] [ j ] ) , ’i’ , ’ ’ , 6 ) ) ;
140 }
141 System . o u t . p r i n t l n ( ) ;
142 }
143 // P r i m e r o c a l c u l a un v e c t o r de máximos p o r r e n g l ó n
144 i n t [ ] deLosMax = {maximo ( numeros [ 0 ] ) , // I n i c i a l i z a
145 maximo ( numeros [ 1 ] ) ,
146 maximo ( numeros [ 2 ] ) ,
147 maximo ( numeros [ 3 ] ) ,
148 maximo ( numeros [ 4 ] ) } ;
149 // Ahora c a l c u l a e l máximo d e l v e c t o r de máximos
150 System . o u t . p r i n t l n ( "El maximisimo es :\t"
151 + maximo ( deLosMax ) ) ;
152 }
153 }

En la figura 6.18 en la siguiente página se encuentra lo que se imprime des-


de main en la clase Arreglos2, para ejemplificar el uso de arreglos pasados como
6.3 Aspectos principales de la herencia 300

parámetros.

Figura 6.18 Salida de Arreglos2


Suma de c o l u m n a s de m a t r i z
con r e n g l o n e s de d i s t i n t o s tamaños
La m a t r i z c o n t i e n e :
2 3 1
3
5 2 8 4
6 5 4 3 2 1
3
=========================================
La suma e s :
19 10 13 7 2 1
Ahora l a suma p o r c o l u m n a s
19 10 13 7 2 1
==================================
Una m a t r i z g e n e r a d a a l e a t o r i a m e n t e :
957 544 227 728
212 155 112 85
576 790 383 887
270 557 484 52
262 226 92 829
E l maximisimo e s : 957

6.3 Aspectos principales de la herencia

Como mencionamos antes, uno de los objetivos principales de la herencia es el


poder reutilizar clases ya definidas. Generalmente nos encontramos que algo que
ya está hecho “casi” nos sirve, excepto que le falta algún dato o algún formato de
salida. En el segundo capı́tulo mencionamos que la herencia conforma un árbol
jerárquico que, en el caso de Java que presenta herencia simple –una clase o interfaz
sólo puede heredar de una sola clase o interfaz “superior” en la jerarquı́a– tiene
como raı́z a la clase Object –ver la definición en la página de Java–. Toda clase
de Java hereda directa o indirectamente de Object, pero como esto es común a
toda clase se omite esta relación. Se explicita únicamente la herencia directa y se
asume la lı́nea de herencia de la clase a la que se extiende.
Si regresamos a nuestro ejemplo de la base de datos para listas de cursos, ve-
remos que hay muchas otras listas que se nos ocurre hacer y que compartirı́an la
misma información básica. Quitemos la clave de usuario porque esa información
301 Herencia

no la necesitan más que en el centro de cómputo. Procediendo como hasta ahora,


definimos una interfaz que liste los servicios de la clase básica, que se muestra en
el listado 6.9 en la siguiente página. En él encontrarán los métodos de acceso tra-
dicionales y otros más que regresan los atributos, pero editados, para formar parte
de las columnas de una tabla. No consideramos necesario mostrar nuevamente la
tarjeta de responsabilidades, pues es exactamente la misma que en el caso que
vimos antes.
6.3 Aspectos principales de la herencia 302

Código 6.9 Interfaz para el registro básico de Estudiante ServEstBasico (1/2)

10 package H e r e n c i a ;
20 /∗ ∗
30 ∗ I n t e r f a c e <code>S e r v E s t B a s i c o </code> d e s c r i b e l o s s e r v i c i o s
40 ∗ que d a r á l a c l a s e <code>E s t u d i a n t e </code >.
50 ∗/
60 p u b l i c i n t e r f a c e S e r v E s t B a s i c o {
70 /∗ ∗
80 ∗ Método <code>getNombre </code> r e g r e s a e l v a l o r d e l a t r i b u t o
90 ∗ <code>nombre</code >.
100 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >.
110 ∗/
120 p u b l i c S t r i n g getNombre ( ) ;
130
140 /∗ ∗
150 ∗ Método <code>g e t C u e n t a </code> r e g r e s a e l v a l o r d e l a t r i b u t o
160 ∗ <code>c u e n t a </code >.
170 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
180 ∗/
190 public S t r i n g getCuenta ( ) ;
200
210 /∗ ∗
220 ∗ Método <code>g e t C a r r e r a </code> r e g r e s a e l v a l o r d e l a t r i b u t o
230 ∗ <code>c a r r e r a </code >.
240 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
250 ∗/
260 public int getCarrera ( ) ;
270
280 /∗ ∗
290 ∗ Método <code>setNombre </code> a c t u a l i z a e l v a l o r d e l a t r i b u t o
300 ∗ <code>nombre</code >.
310 ∗ @param n t i p o <code>S t r i n g </code >: nuevo v a l o r de nombre .
320 ∗/
330 p u b l i c v o i d setNombre ( S t r i n g n ) ;
340
350 /∗ ∗
360 ∗ Método <code>s e t C u e n t a </code> a c t u a l i z a e l v a l o r d e l a t r i b u t o
370 ∗ <code>c u e n t a </code >.
380 ∗ @param c t i p o <code>S t r i n g </code >: nuevo v a l o r de c u e n t a .
390 ∗/
400 public void setCuenta ( S t r i n g c ) ;
410
420 /∗ ∗
430 ∗ Método <code>s e t C a r r e r a </code> a c t u a l i z a e l v a l o r
303 Herencia

Código 6.9 Interfaz para el registro básico de Estudiante ServEstBasico (2/2)

140 ∗ del atributo carrera .


150 ∗ @param c t i p o <code>i n t </code >: nuevo v a l o r de c a r r e r a .
160 ∗/
170 public void s e t C a r r e r a ( i n t c ) ;
180
190 /∗ ∗
200 ∗ Método <code>daNombre</code> r e g r e s a una c a d e n a
210 ∗ r e l l e n a d a con b l a n c o s a l a d e r e c h a .
220 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: nombre e d i t a d o .
230 ∗/
240 p u b l i c S t r i n g daNombre ( ) ;
250
260 /∗ ∗
270 ∗ Método <code>daCuenta </code> arma una c a d e n a de tamaño
280 ∗ f i j o r e l l e n a n d o con c e r o s a l a i z q u i e r d a .
290 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c u e n t a r e l l e n a d a .
300 ∗/
310 p u b l i c S t r i n g daCuenta ( ) ;
320
330 /∗ ∗
340 ∗ Método <code>d a C a r r e r a </code> r e g r e s a e l nombre de l a
350 ∗ c a r r e r a con un tamaño f i j o , r e l l e n a d o de b l a n c o s a l a
360 ∗ derecha .
370 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a r r e r a e d i t a d a .
380 ∗/
390 public String daCarrera ( ) ;
400
410 /∗ ∗
420 ∗ Método <code>t o S t r i n g </code> c o n v i e r t e a una c a d e n a a l o b j e t o .
430 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a d e n a que d e s c r i b e e l e s t a d o
440 ∗ del objeto .
450 ∗/
460 public String toString ( ) ;
470
480 /∗ ∗ Método <code>daCampo</code >: r e g r e s a e l campo s o l i c i t a d o .
490 ∗ @param c u a l t i p o <code>i n t </code >: s e l e c t o r d e l
500 ∗ campo a r e g r e s a r .
510 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: e l c o n t e n i d o
520 ∗ d e l campo e d i t a d o en una c a d e n a .
530 ∗/
540 p u b l i c S t r i n g daCampo ( i n t c u a l ) ;
550
560 /∗ ∗
570 ∗ Método <code>ponCampo</code >: a c t u a l i z a e l campo s o l i c i t a d o
580 ∗ con l a i n f o r m a c i ó n p r o p o r c i o n a d a .
590 ∗ @param c u a l t i p o <code>i n t </code> p a r a e l e g i r campo .
600 ∗ @param v a l o r t i p o <code>S t r i n g </code> p a r a d a r e l nuevo v a l o r .
610 ∗/
620 p u b l i c v o i d ponCampo ( i n t c u a l , S t r i n g v a l o r ) ;
630 }
6.3 Aspectos principales de la herencia 304

Entre los servicios ofrecidos por esta interfaz tenemos el método toString(),
que pretende armar el registro completo para impresión. Este método se hereda
de la clase Object que, por omisión, en caso de una variable primitiva la convierte
a cadena y en caso de una referencia, convierte a esta dirección también a cadena.
Como toda clase extiende a Object, y lo que pretende este método es convertir al
objeto en una cadena observable, lo que hacemos es “redefinir” el método para
que en todos lados donde se invoque –por ejemplo, dentro del método println– se
use esta nueva manera de mostrar el estado del objeto en cuestión. Notarán la
ausencia del método armaRegistro() que es al que sustituye toString(). Como el
objetivo de la herencia es, precisamente, reutilizar, elegimos redefinir el método
toString() de la superclase Object para producir una cadena que refleje el contenido
de objetos de la clase EstudianteBasico –lı́neas 460–. La clase Object es la raı́z del
árbol de herencia: toda clase extiende a la clase Object.

Dados estos servicios, tenemos una clase que los ofrece, EstudianteBasico, que
quedarı́a definida como se muestra en el listado 6.10 en la página opuesta. Omi-
timos los comentarios para tener un código más fluido y porque son métodos
que responden a lo especificado en la interfaz. únicamente comentaremos aquellos
métodos adicionales a los presentados en la interfaz.
305 Herencia

Código 6.10 Superclase EstudianteBasico EstudianteBasico (1/4)


10 package H e r e n c i a ;
20 import j a v a . u t i l . S c a n n e r ;
30 import u t i l e s . ∗ ;
40 /∗ ∗
50 ∗ C l a s e <code>E s t u d i a n t e B a s i c o </code> r e p r e s e n t a a un r e g i s t r o de l a
60 ∗ b a s e de d a t o s , b a s a d a en l i s t a s de r e g i s t r o s , que emula l a l i s t a de
70 ∗ un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s n o r m a l e s de una b a s e
80 ∗ de d a t o s y f u n c i o n a m e d i a n t e un Menú .
90 ∗ @version 2.0
100 ∗/
110 p u b l i c c l a s s E s t u d i a n t e B a s i c o implements S e r v E s t B a s i c o {
120 /∗ Para e d i c i ó n d e l r e g i s t r o ∗/
130 public static f i n a l int
140 TAM NMBRE = 3 6 , // Para m o s t r a r
150 TAM CTA = 9 , // Para m o s t r a r
160 TAM CARRERA = C a t a l o g o C a r r e r a s .TAM NOMBRE; // Para m o s t r a r
170 /∗ A t r i b u t o s de l a c l a s e ∗/
180 p r i v a t e S t r i n g nombre ; /∗ Nombre d e l e s t u d i a n t e . ∗/
190 private S tr i n g cuenta ; /∗ Número de c u e n t a d e l e s t u d i a n t e . ∗/
200 private int carrera ; /∗ C a r r e r a que c u r s a . ∗/
210
220 /∗ ∗ C o n s t a n t e s que i d e n t i f i c a n a l o s campos ∗/
230 public static f i n a l int
6.3 Aspectos principales de la herencia 306

Código 6.10 Superclase EstudianteBasico EstudianteBasico (2/4)


240 NOMBRE = 1 ,
250 CUENTA = 2 ,
260 CARRERA = 3 ;

320 public EstudianteBasico () {


330 nombre = c u e n t a = "" ;
340 }

460 p u b l i c E s t u d i a n t e B a s i c o ( S t r i n g nmbre ,
470 S t r i n g cuenta , i n t c a r r e r a ) {
480 nombre = nmbre == n u l l ? "" : nmbre ;
490 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a ;
500 i f ( CatalogoCarreras . esCarrera ( carrera )) {
510 this . carrera = carrera ;
520 } // S i no l e ponemos nada queda en 0
530 }

610 public EstudianteBasico ( EstudianteBasico copia ) {


620 nombre = c o p i a . nombre ;
630 carrera = copia . carrera ;
640 cuenta = copia . cuenta ;
650 }
710 public S t r i n g getCuenta () {
720 return cuenta ;
730 }

810 p u b l i c v o i d s e t C u e n t a ( S t r i n g newCuenta ) {
820 t h i s . c u e n t a = newCuenta == n u l l ? "" : newCuenta ;
830 }

900 public int getCarrera () {


910 return c a r r e r a ;
920 }

1000 public void s e t C a r r e r a ( i n t newCarrera ) {


1010 i f ( CatalogoCarreras . e sCar r er a ( newCarrera )) {
1020 th is . c a r r e r a = newCarrera ;
1030 } else {
1040 this . carrera = 0;
1050 }
1060 }

1130 p u b l i c S t r i n g getNombre ( ) {
1140 r e t u r n nombre ;
1150 }

1230 p u b l i c v o i d setNombre ( S t r i n g newNombre ) {


1240 t h i s . nombre = newNombre == n u l l ? "" : newNombre ;
1250 }
307 Herencia

Código 6.10 Superclase EstudianteBasico EstudianteBasico (3/4)

1270 /∗ ∗
1280 ∗ Método <code>t o S t r i n g </code >: p a r a no p e r d e r a c c e s o a l
1290 ∗ método de l a s u p e r c l a s e .
1300 ∗ @param c t i p o <code>i n t </code >: p a r a d i s t i n g u i r métodos .
1310 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a d e n a de l a s u p e r c l a s e .
1320 ∗/
1330 public String toString ( int c ) {
1340 r e t u r n super . t o S t r i n g ( ) ;
1350 }

1270 /∗ ∗
1280 ∗ Método <code>t o S t r i n g </code >, r e d e f i n e e l mismo método en l a
1290 ∗ c l a s e O b j e c t y s u s t i t u y e a l método a r m a R e g i s t r o d e l c a p i t u l o
1300 ∗ anterior .
1310 ∗ @ r e t u r n t i p o <code>S t r i n g </code >.
1320 ∗/
1330 public String toString () {
1340 S t r i n g l i n e a = daNombre ( )
1350 + " " + daCuenta ( )
1360 + " " + daCarrera ( ) ;
1370 return l i n e a ;
1380 }

1560 p u b l i c S t r i n g daCampo ( i n t c u a l ) {
1570 switch ( c u a l ) {
1580 case NOMBRE: r e t u r n daNombre ( ) . t r i m ( ) ;
1590 case CUENTA : r e t u r n daCuenta ( ) . t r i m ( ) ;
1600 case CARRERA : return daCarrera ( ) . trim ( ) ;
1610 default : r e t u r n " Numero de campo invalido " ;
1620 }
1630 }

1710 p u b l i c v o i d ponCampo ( i n t c u a l , S t r i n g v a l o r ) {
1720 i f ( v a l o r == n u l l ) { // Para no m a n e j a r r e f e r e n c i a s n u l a s .
1730 v a l o r = "" ;
1740 }
1750 switch ( c u a l ) {
1760 case NOMBRE: setNombre ( v a l o r ) ;
1770 break ;
1780 case CUENTA : setCuenta ( v a l o r ) ;
1790 break ;
1800 case CARRERA :
1810 int intValor = Integer . parseInt ( valor );
1820 i f ( CatalogoCarreras . esCarrera ( intValor )) {
1830 setCarrera ( intValor );
1840 } else {
1850 setCarrera (0);
1860 }
6.3 Aspectos principales de la herencia 308

Código 6.10 Superclase EstudianteBasico EstudianteBasico (4/4)


1870 default : System . e r r . p r i n t l n ( " Número de campo inválido "
1880 + cual );
1890 break ;
1900 }
1910 }

1990 p u b l i c S t r i n g daNombre ( ) {
2000 r e t u r n Cadenas . r e l l e n a ( nombre , ’i’ , ’ ’ , TAM NMBRE ) ;
2010 }

2080 public String daCarrera () {


2090 S t r i n g nCarre = CatalogoCarreras . daCarrera ( c a r r e r a ) ;
2100 return nCarre ;
2110 }

2190 p u b l i c S t r i n g daCuenta ( ) {
2200 S t r i n g sCuenta = S t r i n g . valueOf ( cuenta ) ;
2210 i f ( s C u e n t a . l e n g t h ( ) < TAM CTA) {
2220 r e t u r n Cadenas . r e l l e n a ( sCuenta , ’d’ , ’0’ , TAM CTA ) ;
2230 }
2240 else {
2250 r e t u r n s C u e n t a . s u b s t r i n g ( 0 ,TAM CTA ) ;
2260 }
2270 }

2390 p u b l i c v o i d p o n R e g i s t r o ( S t r i n g nmbre , i n t c a r r e r a , S t r i n g c u e n t a ) {
2400 nombre = nmbre == n u l l ? "" : nmbre . t r i m ( ) ;
2410 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a . t r i m ( ) ;
2420 this . setCarrera ( carrera );
2430 }\ v s {  .75 ex }

Lo único que debemos notar es el uso del acceso protected en los atributos de
esta clase. Cuando tenemos extensión de clases, como pretendemos hacer ahora,
tiene sentido hablar de este modo de acceso que hemos mencionado poco hasta
ahora. Este tipo de acceso se usa para cuando queremos que las clases que extien-
den a la actual puedan tener acceso a ciertas variables o métodos, ya que funciona
como si fuera acceso público para las clases que extienden, pero como si fuera
privado para las clases que no extienden a la clase actual. Los datos con acceso
privado no son accesibles más que para la clase actual. El acceso protegido fun-
ciona como el privado para clases que no heredan y como público para las clases
que heredan.
Se nos ocurren distintos tipos de listas que tomen como base a EstudianteBasico.
Por ejemplo, un listado que le pudiera servir a una biblioteca tendrı́a, además de la
información de EstudianteBasico, los libros en préstamo. Un listado para la División
309 Herencia

de Estudios Profesionales deberı́a tener una historia académica y el primer año


de inscripción a la carrera. Un listado para calificar a un grupo deberı́a tener un
cierto número de campos para las calificaciones. Por último, un listado para las
salas de cómputo deberı́a contar con la clave de acceso.

En todos los casos que listamos, la información original debe seguir estando
presente, ası́ como los métodos que tiene la superclase. Debemos indicar, entonces,
que la clase que se está definiendo extiende a la superclase, heredando por lo tanto
todo aquello declarado como protegido, público o de paquete. La sintaxis en Java
se muestra a continuación.

Sintaxis:
xsubclase que hereday ::= class xsubclasey extends xsuperclasey {
xdeclaraciones de campos y métodosy
}
Semántica:
La xsubclasey es una nueva declaración, que incluye (hereda) a todos los
campos de la superclase (no–privados), junto con todos sus métodos. Las
xdeclaraciones de campos y métodosy se refiere a lo que se desea agregar a la
definición de la superclase en esta subclase. También colocamos ahı́ firmas
de métodos que existen en la superclase pero queremos funcionen un poco
distinto en la subclase; atributos cuyo nombre aparece en la superclase y
que pueden o no cambiar de tipo; y, en general, todas aquellas declaraciones
cuyo nombre o firma ya aparezca en la superclase pero queremos que tenga
otro significado en la subclase.

Si alguna de las declaraciones hechas en la xsubclasey coincide en tipo o fir-


ma con algo declarado en la xsuperclasey, entonces los campos o métodos de la
superclase quedarán “tapados” o escondidos (redefinidos) para la xsubclasey.

Pongamos por ejemplo la programación de una subclase para las listas con cali-
ficaciones. El registro deberá contener lo que contiene EstudianteBasico, agregándo-
le nada más lugar para las calificaciones de los exámenes. El encabezado de la clase
quedarı́a como se ve en el listado 6.11 en la siguiente página.
6.3 Aspectos principales de la herencia 310

Código 6.11 Encabezado para la subclase EstudianteCalifs EstudianteCalifs (1/2)


10 package H e r e n c i a ;
20 import u t i l e s . Cadenas ;
30 /∗ ∗
40 ∗ c l a s s <code>E s t u d i a n t e C a l i f s </code >: e x t i e n d e a t i p o
50 ∗ <code>E s t u d i a n t e B a s i c o </code> p a r a p o d e r r e g i s t r a r
60 ∗ c a l i f i c a c i o n e s .
70 ∗/

Código 6.11 Encabezado para la subclase EstudianteCalifs EstudianteCalifs (2/2)

80 p u b l i c c l a s s E s t u d i a n t e C a l i f s extends E s t u d i a n t e B a s i c o {
/∗ D e c l a r a c i o n e s a d i c i o n a l e s ∗/
...
/∗ A g r e g a rı́ a m o s l o s métodos que t i e n e n que v e r con l a s ∗/
/∗ c a l i f i c a c i o n e s y a q u e l l o s a t r i b u t o s que hayamos ∗/
/∗ a g r e g a d o . ∗/
...
99990 }

No es necesario especificar que la clase implementa a ServEstBasico, pues por el


hecho de que extiende a EstudianteBasico, implementa a todas las interfaces que se
especifiquen en EstudianteBasico –y a todas aquellas interfaces de las superclases–.
A partir de ese momento podemos suponer que EstudianteCalifs contiene todos los
campos y métodos que tiene EstudianteBasico. Los únicos métodos que no hereda
implı́citamente son los constructores. Si en la superclase se definió explı́citamente
un constructor sin parámetros o se mantuvo el que da por omisión Java –siempre
y cuando no se haya declarado ningún constructor–, éste va a ser invocado siem-
pre que se invoque algún constructor de la subclase EstudianteCalifs y antes de
que se ejecute algo más en el constructor de la subclase, a menos que como pri-
mer enunciado dentro del constructor se haga una llamada explı́cita a alguno de
los constructores de la superclase. Debemos recordar que si la superclase sólo
tiene constructores con parámetros, queda oculto e inaccesible el constructor sin
parámetros, por lo que es conveniente declarar uno para uso de las subclases o
habrá un mensaje del compilador de sı́mbolo no declarado. Regresaremos a esto
en cuanto programemos los constructores para la subclase.
Cuando un método de una superclase tiene un cierto acceso –public, private,
protected o de paquete–, si se redefine ese método en una subclase el acceso no
puede restringirse. Esto es, si en la superclase el método tenı́a acceso público, en
la subclase deberá mantener ese mismo acceso. Si en la superclase tenı́a acceso de
paquete, en la subclase puede tener acceso de paquete o público, pero el acceso
311 Herencia

para el método o atributo que se redefine no puede ser protegido ni privado. Se


puede hacer menos restringido el acceso pero no restringirlo más.

6.3.1. Súper y subclases

Tenemos ya las herramientas necesarias para hacer las declaraciones de nues-


tras subclases, ası́ que proseguiremos con la construcción de un registro de estu-
diante para ser usado en un curso, para lo que debemos agregar a los atributos
en la subclase EstudianteCalifs la declaración de un arreglo para guardar las ca-
lificaciones del estudiante. También desarrollaremos el constructor de la subclase
–ver listado 6.12 en la siguiente página–. Cuando se crea un objeto cuyo tipo es
una subclase, lo primero que hace el objeto es, como ya mencionamos, invocar
al constructor sin parámetros de la superclase. Si no hay tal –porque hayamos
declarado constructores con parámetros únicamente– el constructor de la subclase
deberá llamar explı́citamente a algún constructor definido en la superclase, pues
de otra manera no compilará. Esto se hace con el identificador super seguido por
la lista de argumentos entre paréntesis y tiene que ser el primer enunciado en el
constructor de la subclase.

Supongamos que el número de calificaciones que deseamos guardar para un


estudiante dado depende del curso que esté tomando. Entonces, la creación del
arreglo de calificaciones se harı́a en el constructor, como se puede ver en el lista-
do 6.12 en la siguiente página, donde codificamos dos constructores, el que trabaja
sin parámetros y el que recibe los parámetros especificados.
6.3 Aspectos principales de la herencia 312

Código 6.12 Campos y constructor de EstudianteCalifs EstudianteCalifs


...
120 p u b l i c c l a s s E s t u d i a n t e C a l i f s extends E s t u d i a n t e B a s i c o {
130 p r o t e c t e d f l o a t [ ] c a l i f s ; /∗ A r r e g l o p a r a l a s c a l i f i c a c i o n e s ∗/
140
150 /∗ ∗
160 ∗ C o n s t r u y e un nuevo e j e m p l a r de <code>E s t u d i a n t e C a l i f s </code>
170 ∗ v a cı́ o .
180 ∗/
190 public Es tu d i a n t eC a l i f s () {
200 /∗ En a u t o m á t i c o i n v o c a a l c o n s t r u c t o r s i n p a r á m e t r o s que s e
210 ∗ d e c l a r ó y d e j a a l a r e f e r e n c i a a l a r r e g l o en n u l l ∗/
220 }
230
240 /∗ ∗
250 ∗ C o n s t r u y e un nuevo e j e m p l a r de <code>E s t u d i a n t e C a l i f s </code >.
260 ∗ I n v o c a a l c o n s t r u c t o r de l a s u p e r c l a s e con l o s p a r á m e t r o s
270 ∗ que c o r r e s p o n d e n a l a s u p e r c l a s e .
280 ∗ @param nvoNombre t i p o <code>S t r i n g </code >: v a l o r p a r a e l campo
290 ∗ <code>nombre</code >.
300 ∗ @param n v a C a r r e r a t i p o <code>i n t </code >: v a l o r p a r a e l campo
310 ∗ <code>c a r r e r a </code >.
320 ∗ @param n u m C a l i f s t i p o <code>i n t </code >: v a l o r p a r a e l número de
330 ∗ c a l i f i c a c i o n e s a g u a r d a r ( tamaño d e l a r r e g l o ) .
340 ∗/
350 p u b l i c E s t u d i a n t e C a l i f s ( S t r i n g nvoNombre , S t r i n g nvaCuenta ,
360 int nvaCarrera , int numCalifs ) {
370 super ( nvoNombre , nvaCuenta , n v a C a r r e r a ) ;
380 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
390 }

Una vez hecho y “extendido” el constructor para la subclase, se agregan toda


clase de métodos que manejan a la parte de atributos que se extendió en la sub-
clase. La mayorı́a de los métodos nuevos únicamente manipulan al nuevo campo
y los explicaremos a continuación del listado 6.13 en la página opuesta.
313 Herencia

Código 6.13 Métodos nuevos para la subclase EstudianteCalifs EstudianteCalifs (1/3)


410 /∗ ∗
420 ∗ Método <code>g e t C a l i f s </code >, r e g r e s a e l a r r e g l o con l a s
430 ∗ calificaciones .
440 ∗ @ r e t u r n t i p o <code>f l o a t [] </ code >, e l a r r e g l o .
450 ∗/
460 public float [ ] g e t C a l i f s () {
470 return c a l i f s ;
480 }
490
500 /∗ ∗
510 ∗ Método <code>s e t C a l i f s </code >, c o p i a l a r e f e r e n c i a a un a r r e g l o
520 ∗ de c a l i f i c a c i o n e s .
530 ∗ @param c a l i f s t i p o <code>f l o a t </code >, l a r e f e r e n c i a a c o p i a r .
540 ∗/
550 public void s e t C a l i f s ( f l o a t [ ] c a l i f s ) {
560 this . c a l i f s = c a l i f s ;
570 }
580
590 /∗ ∗
600 ∗ Método <code>c o p i a C a l i f s </code> c o p i a l a s c a l i f i c a c i o n e s , una a
610 ∗ una , d e l a r r e g l o dado .
620 ∗ @param c a l i f s t i p o <code>f l o a t </code> a r r e g l o con
630 ∗ c a l i f i c a c i on e s a copiar .
640 ∗/
650 public void c o p i a C a l i f s ( f l o a t [ ] c a l i f s ) {
660 i f ( c a l i f s == n u l l ) {
670 this . c a l i f s = null ;
680 return ;
690 }
700 t h i s . c a l i f s = new f l o a t [ c a l i f s . l e n g t h ] ;
710 f o r ( i n t i = 0 ; i < c a l i f s . l e n g t h ; i ++) {
720 this . c a l i f s [ i ] = c a l i f s [ i ] ;
730 }
740 }
750
760 /∗ ∗
770 ∗ Método <code>c o p i a </code> r e a l i z a una c o p i a ( c l o n a d o ) de un
780 ∗ a r r e g l o de f l o a t de una d i m e n s i ó n .
790 ∗ @param v e c t o r t i p o <code>f l o a t </code> a r r e g l o a c o p i a r .
800 ∗ @ r e t u r n t i p o <code>f l o a t [] </ code >, e l a r r e g l o c l o n a d o .
810 ∗/
820 public static float [ ] copia ( float [ ] vector ) {
830 i f ( v e c t o r == n u l l ) {
6.3 Aspectos principales de la herencia 314

Código 6.13 Métodos nuevos para la subclase EstudianteCalifs EstudianteCalifs (2/3)


840 return n u l l ;
850 }
860 f l o a t [ ] v e c t C o p i a = new f l o a t [ v e c t o r . l e n g t h ] ;
870 f o r ( i n t i = 0 ; i < v e c t o r . l e n g t h ; i ++) {
880 vectCopia [ i ] = vector [ i ] ;
890 }
900 return vectCopia ;
910 }
920
930 /∗ ∗
940 ∗ Método <code>promedio </code >, o b t i e n e e l p r o m e d i o de l a s
950 ∗ calificaciones .
960 ∗ @ r e t u r n t i p o <code>f l o a t </code >, e l p r o m e d i o .
970 ∗/
980 public f l o a t promedio ( ) {
990 i f ( c a l i f s == n u l l )
1000 return 0;
1010 f l o a t suma = 0 ;
1020 f o r ( i n t i =0; i < c a l i f s . l e n g t h ; i ++) {
1030 suma += c a l i f s [ i ] ;
1040 }
1050 r e t u r n suma / c a l i f s . l e n g t h ;
1060 }
1070
1080 /∗ ∗
1090 ∗ Método <code>s e t C a l i f </code> que c o l o c a una c a l i f i c a c i ó n .
1100 ∗ @param c u a l t i p o <code>f l o a t </code >, l a c a l i f i c a c i ó n .
1110 ∗ @param donde t i p o <code>i n t </code >, l a p o s i c i ó n .
1120 ∗/
1130 p u b l i c v o i d s e t C a l i f ( f l o a t c u a l , i n t donde ) {
1140 i f ( c a l i f s == n u l l | | donde < 0 | | donde > c a l i f s . l e n g t h )
1150 return ;
1160 c a l i f s [ donde ] = c u a l < 0 ? 0 : Math . min ( c u a l , 1 0 . 0 f ) ;
1170 }
1180
1190 /∗ ∗
1200 ∗ Método <code>g e t C a l i f </code >, o b t i e n e una c a l i f i c a c i ó n .
1210 ∗ @param donde t i p o <code>i n t </code >, p o s i c i ó n de c a l i f i c a c i ó n .
1220 ∗ @ r e t u r n v a l o r t i p o <code>f l o a t </code >, v a l o r de c a l i f i c a c i ó n
1230 ∗ en l a p o s i c i ó n dada .
1240 ∗/
1250 p u b l i c f l o a t g e t C a l i f ( i n t donde ) {
1260 i f ( c a l i f s != n u l l && donde >= 0 && donde < c a l i f s . l e n g t h )
1270 r e t u r n c a l i f s [ donde ] ;
1280 r e t u r n  1;
1290 }
1300
1310 /∗ ∗
1320 ∗ Método <code>t o S t r i n g </code >, r e d e f i n e e l método
1330 ∗ <code>t o S t r i n g </code> de l a s u p e r c l a s e . Usa l o que ya t i e n e
315 Herencia

Código 6.13 Métodos nuevos para la subclase EstudianteCalifs EstudianteCalifs (3/3)

1320 ∗ de l a s u p e r c l a s e ( super ) y l e a g r e g a l a p a r t e que f a l t a .


1330 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: l a c a d e n a que r e p r e s e n t a e l
1340 ∗ estado del objeto .
1350 ∗/
1360 public String toString () {
1370 S t r i n g l i n e a = super . t o S t r i n g ( ) ;
1380 f o r ( i n t i = 0 ; i < c a l i f s . l e n g t h ; i ++) {
1390 l i n e a = l i n e a + Cadenas . f o r m a t o ( c a l i f s [ i ] , ’ ’ , 7 , 2 ) ;
1400 }
1410 l i n e a = l i n e a + Cadenas . f o r m a t o ( p r o m e d i o ( ) , ’ ’ , 5 , 2 ) ;
1420 return l i n e a ;
1430 }

Hay varios métodos “nuevos” en el listado 6.13 que acabamos de mostrar y


notarán la ausencia del método armaRegistro. Como el objetivo de la herencia es,
precisamente, reutilizar, elegimos redefinir el método toString() de la superclase
EstudianteBasico para producir una cadena que refleje el contenido de objetos de
la clase EstudianteCalifs –lı́neas 1370 a 1430–. El significado de esto es que una vez
que describa en una cadena a un EstudianteBasico, le deberá pegar lo agregado en
EstudianteCalifs.
Agregamos también los métodos get y set para el campo nuevo. El problema
es que el atributo califs es un objeto –un arreglo– por lo que los métodos getCalifs
y setCalifs manejan la referencia, no el objeto en sı́. Para poder generar arreglos
“frescos” que se refieran a otro arreglo en el heap y no al mismo, agregamos un
método copiaCalifs que crea espacio nuevo y copia los valores –lı́neas 650 a 740–.
Por último, agregamos métodos que tienen acceso a elementos del arreglo, tanto
para actualizar como para obtener el valor –setCalif(int, float) y getCalif(int)–.

6.4 Polimorfismo
Una de las grandes ventajas que nos ofrece la herencia es poder manipular
subclases a través de las superclases, sin que sepamos concretamente de cuál sub-
clase se trata. Supongamos, para poner un ejemplo, que declaramos otra subclase
de EstudianteBasico que se llama EstudianteBiblio y que de manera similar a como
lo hicimos con EstudianteCalifs extendemos adecuadamente y redefinimos nueva-
mente el método toString(). Podrı́amos tener el código que sigue:
10 EstudianteBasico [ ] estudiantes = {
20 new E s t u d i a n t e C a l i f s ( " Pedro ’’,. . . ),
30 new EstudianteBiblio (. . . )
40 };
6.4 Polimorfismo 316

En estas lı́neas declaramos un arreglo con dos elementos del tipo EstudianteBasico,
donde el primero contiene a un EstudianteCalifs mientras que el segundo contiene
a un EstudianteBiblio. Como las subclases contienen todo lo que contiene la super-
clase, y como lo que guardamos son referencias, podemos guardar en un arreglo de
la superclase elementos de las subclases. Es más; si hacemos la siguiente solicitud

S t r i n g cadena = e s t u d i a n t e s [ 0 ] . t o S t r i n g ( ) ;

la máquina virtual de Java, en ejecución, se da cuenta que lo que tiene ahı́ es una
referencia a algo del tipo EstudianteCalifs, por lo que utilizará la implementación
dada en esa subclase para dar respuesta a esta solicitud. Decimos que gobierna
el tipo del objeto construido, no el tipo de la declaración. La decisión de cuál
de las implementaciones de toString() debe ser invocada se toma en ejecución, ya
que el tipo de la referencia guardada en la localidad del arreglo depende de la
secuencia de ejecución . A esta capacidad de los lenguajes orientados a objetos
de resolver dinámicamente el significado de un nombre de método es a lo que se
llama polimorfismo, ya que el mismo nombre (de hecho, la misma firma) puede
tomar significados distintos dependiendo del estado del programa y de la clase a
la que vaya dirigido el mensaje (la llamada o solicitud).

El operador instanceof
Supongamos que estamos en la sección escolar de la Facultad de Ciencias,
donde tienen registros de alumnos de distintos tipos; supongamos asimismo que
le piden al coordinador que por favor entregue una lista de todos los registros que
tiene para la biblioteca. El coordinador deberá tener un mecanismo que identifique
de qué clase es cada uno de los objetos que se encuentran en la lista (arreglo,
lista ligada, etc.). El operador instanceof hace exactamente esto. Es un operador
binario, donde las expresiones que lo usan toman la siguiente forma:

xexpresión de objetoy instanceof xidentificador de clasey

que regresa el valor booleano verdadero si, en efecto, el objeto dado en la expresión
de la izquierda es un ejemplar de la clase dada a la derecha; y falso si no. Para un
proceso como el que acabamos de describir tendrı́amos un código como el que se
puede observar en el listado 6.14 en la página opuesta –suponiendo que tenemos
los registros en un arreglo de registros–.
317 Herencia

Código 6.14 Registros en un arreglo


EstudianteBasico [ ] estudiantes ;
/∗ Acá s e i n i c i a l i z a e l a r r e g l o y s e van a g r e g a n d o l o s
∗ estudiantes .
∗/
int i ;
String reporte ;
f o r ( i = 0 , r e p o r t e = "" ; i < e s t u d i a n t e s . l e n g t h ; i ++) {
i f ( e s t u d i an t e s [ i ] instanceof E s t u d i a n t e B i b l i o ) {
r e p o r t e += e s t u d i a n t e s [ i ] . t o S t r i n g ( ) ;
}
}
...

Supongamos ahora que queremos una lista de estudiantes con su calificación


final del curso, pero sin las calificaciones parciales. El método toString no satisface
esto. Tendrı́amos un código similar al anterior en el listado 6.15.

Código 6.15 Otra versión del método toString


EstudianteBasico [ ] estudiantes ;
/∗ ∗
∗ Acá s e i n i c i a l i z a e l a r r e g l o y s e van a g r e g a n d o l o s
∗ estudiantes .
∗/
int i ;
String reporte ;
f o r ( i = 0 , r e p o r t e = "" ; i < e s t u d i a n t e s . l e n g t h ; i ++) {
i f ( e s t u d i an t e s [ i ] instanceof E s t u d i a n t e C a l i f s ) {
/∗ ∗
∗ ¡ Acá hay p r o b l e m a s , p u e s queremos ú n i c a m e n t e e l
∗ nombre d e l e s t u d i a n t e y s u p r o m e d i o ! Pero no hay
∗ manera de a c c e d e r a l método que c o r r e s p o n d e a l a
∗ s u p e r c l a s e . Por l o t a n t o hay que armar l a c a d e n a
∗ a imprimir a pie :
∗/
r e p o r t e += e s t u d i a n t e s [ i ] . daNombre ( ) + "\t"
+ (( EstudianteCalifs ). estudiantes [ i ])
. promedio ( ) ;
}
}
...

El casting que se hizo en la última lı́nea es necesario, porque en el momento de


compilación no se sabe la subclase que corresponderá al elemento en cuestión, por
6.4 Polimorfismo 318

lo que en ejecución se deberá pedir que se “interprete” el registro de esa manera.


Si no lo puede hacer, porque el registro no sea de la subclase que especificamos,
habrá un error de ejecución de InvalidClassException.
En general, cuando tenemos una declaración asociada con una superclase y
queremos hacer uso de métodos o atributos declarados para la subclase (desde un
programa principal u otra clase que no esté relacionada jerárquicamente con la
súper y subclase) tendremos que hacer un casting con el nombre de la subclase para
que en tiempo de compilación identifique que estamos hablando de la subclase.
Como hemos venido haciendo, y para terminar esta sección, programamos
una prueba de la clase EstudianteCalifs en su método main, que se encuentra en el
listado 6.16 en la página 320. Para mostrar algunos aspectos de la herencia se crean
dos arreglos, uno de la clase Object y otro de la clase EstudianteBasico. En ambos
arreglos se pueden guardar objetos de cualquiera de sus subclases. En una primera
ejecución, quisimos aplicar casting con una subclase que no es la que construimos,
lo que nos da un error de ejecución (no puede dar error en compilación porque
es válido tratar de ver un objeto con la máscara de alguna de sus subclases). La
ejecución se encuentra en la figura 6.19 en la página 320, lı́neas 20 a 40.

Código 6.16 Prueba de la clase EstudianteCalifs EstudianteCalifs (1/2)

1470 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
1480 /∗ Prueba de l a c l a s e ∗∗∗∗∗ ∗/
1490 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
1500 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
1510 E s t u d i a n t e B a s i c o [ ] miCurso = new E s t u d i a n t e B a s i c o [ 4 ] ;
1520 Object [ ] misObjetos = new O b j e c t [ 4 ] ;
1530 miCurso [ 0 ] = new E s t u d i a n t e C a l i f s ( ) ;
1540 misObjetos [ 0 ] = new O b j e c t ( ) ; ;
1550 miCurso [ 1 ] = new E s t u d i a n t e B a s i c o ( ) ;
1560 misObjetos [ 1 ] = new E s t u d i a n t e B a s i c o ( ) ;
1570 miCurso [ 2 ] = new E s t u d i a n t e C a l i f s ( " Elisa " , " 56432 " , 1 2 7 , 5 ) ;
1580 m i s O b j e t o s [ 2 ] = new E s t u d i a n t e C a l i f s ( " Elisa " , " 56432 " , 1 2 7 , 5 ) ;
1590 miCurso [ 3 ] = new E s t u d i a n t e C a l i f s ( " Jorge " , "34" , 1 0 6 , 4 ) ;
1600 m i s O b j e t o s [ 3 ] = new E s t u d i a n t e C a l i f s ( " Jorge " , "34" , 1 0 6 , 4 ) ;
1610 // ( ( E s t u d i a n t e C a l i f s ) m i s O b j e t o s [ 0 ] ) . c o p i a C a l i f s ( c a l i f s ) ;
1620 float [ ] c a l i f s = {8.7 f , 6 . 8 f , 9 . 6 f ,8 f ,7 f };
1630 ( ( E s t u d i a n t e C a l i f s ) miCurso [ 2 ] ) . s e t C a l i f s ( c a l i f s ) ;
1640 miCurso [ 0 ] . setNombre ( "Juan" ) ;
1650 miCurso [ 0 ] . s e t C u e n t a ( "987" ) ;
1660 ( ( E s t u d i a n t e C a l i f s ) miCurso [ 0 ] ) . c o p i a C a l i f s
1670 ( ( ( E s t u d i a n t e C a l i f s ) miCurso [ 2 ] ) . g e t C a l i f s ( ) ) ;
1680 miCurso [ 1 ] . setNombre ( "Pepe" ) ;
1690 miCurso [ 1 ] . s e t C a r r e r a ( 2 0 1 ) ;
319 Herencia

Código 6.16 Prueba de la clase EstudianteCalifs EstudianteCalifs (2/2)

1700 f o r ( i n t i= 0 ; i < miCurso . l e n g t h ; i ++) {


1710 String clase ;
1720 i f ( miCurso [ i ] i n s t a n c e o f E s t u d i a n t e C a l i f s ) {
1730 c l a s e = " EstudianteCalifs " ;
1740 } else {
1750 c l a s e = " EstudianteBasico " ;
1760 }
1770 System . o u t . p r i n t l n ( " miCurso [" + i +"], clase : " + c l a s e +"\n"
1780 + ( miCurso [ i ] . t o S t r i n g ( ) ) ) ;
1790 i f ( i < misObjetos . length ) {
1800 i f ( misObjetos [ i ] instanceof E s t u d i a n t e C a l i f s ) {
1810 c l a s e = " EstudianteCalifs " ;
1820 } else i f ( misObjetos [ i ] instanceof EstudianteBasico ) {
1830 c l a s e = " EstudianteBasico " ;
1840 } else {
1850 c l a s e = " Object " ;
1860 }
1870 System . o u t . p r i n t l n ( " misObjetos [" + i +"], clase : "
1880 + c l a s e +"\n"
1890 + ( misObjetos [ i ] . toString ( ) ) ) ;
1900 } // i f
1910 } // f o r
1920 } // main
1930 } // c l a s s

Figura 6.19 Ejecución con la linea 1610 (1/2)

10 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a H e r e n c i a / E s t u d i a n t e
20 C a l i f s
30 E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . C l a s s C a s t E x c e p t i o n : j a v a . l a n g . O b j e c t
c a n n o t be c a s t t o H e r e n c i a . E s t u d i a n t e C a l i f s
40 a t H e r e n c i a . E s t u d i a n t e C a l i f s . main ( E s t u d i a n t e C a l i f s . j a v a : 1 5 6 )
50
60 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a H e r e n c i a / E s t u d i a n t e C a l i f s
70
80 // E j e c u c i ó n con l a l i n e a 156 comentada
90
100 miCurso [ 0 ] , c l a s e : E s t u d i a n t e C a l i f s
110 Juan 987000000 C o d i g o i n v a l i d o
8.70 6.80 9.60 8.00 7.00 8.02
6.5 Clases abstractas 320

Figura 6.19 Ejecución con la linea 1610 (2/2)

120 misObjetos [ 0 ] , c l a s e : Object


130 j a v a . l a n g . Object@1729854
140 miCurso [ 1 ] , c l a s e : E s t u d i a n t e B a s i c o
150 Pepe 000000000 A c t u a r i a
160 misObjetos [ 1 ] , c l a s e : EstudianteBasico
170 000000000 C o d i g o i n v a l i d o
180 miCurso [ 2 ] , c l a s e : E s t u d i a n t e C a l i f s
190 Elisa 564320000 C i e n c i a s de l a Computacion
8.70 6.80 9.60 8.00 7.00 8.02
200 m i s O b j e t o s [ 2 ] , c l a s e : E s t u d i a n t e C a l i f s
210 E l i s a 564320000 C i e n c i a s de l a Computacion
0.00 0.00 0.00 0.00 0.00 0.00
220 miCurso [ 3 ] , c l a s e : E s t u d i a n t e C a l i f s
230 J o r g e 340000000 F i s i c a
0.00 0.00 0.00 0.00 0.00
240 m i s O b j e t o s [ 3 ] , c l a s e : E s t u d i a n t e C a l i f s

Figura 6.20 Ejecución con la linea 1610 comentada

250 J o r g e 340000000 F i s i c a
0.00 0.00 0.00 0.00 0.00

6.5 Clases abstractas

Supongamos que tenemos una jerarquı́a de clases para las figuras geométricas,
que se podrı́a ver como en la figura 6.21 en la siguiente página. La superclase es
FiguraGeometrica y reconocemos que toda figura geométrica debe tener los siguien-
tes servicios:
dibujarse,
moverse,
borrarse,
crearse.
321 Herencia

Figura 6.21 Jerarquı́a de clases

Figura geométrica

Lı́nea Superficie Volumen

recta elipse parábola cubo icosaedro cilindro

cuadrado cı́rculo rectángulo

En el caso de las lı́neas podemos obtener su longitud. En el caso de las su-


perficies, podemos obtener su perı́metro y su área. En el caso de los volúmenes,
podemos querer obtener su volumen. Por ejemplo, en el caso de las superficies
podemos tener una lista de puntos que las definen. Y ası́ sucesivamente.
Cuando estamos en la raı́z del árbol realmente todo lo que podemos decir es
que se trata de una figura geométrica. Se trata de una clase abstracta de la que
no podemos decir mucho todavı́a y de la que no puede haber objetos que sean,
en abstracto, una figura geométrica. Conforme vamos bajando por el árbol hacia
las hojas se van concretando algunas de las caracterı́sticas, pero en el caso de la
jerarquı́a que nos ocupa, en el segundo nivel todavı́a no podemos tener objetos que
sean una “superficie” si no decimos qué clase de superficie es. En la figura 6.21 los
nodos ovalados representan clases abstractas, mientras que los nodos rectangulares
representan clases concretas.
En una jerarquı́a cualquiera de clases tenemos las mismas caracterı́sticas que
hemos visto hasta ahora, donde cada subclase hereda todo lo heredable de la
superclase, excepto que para uno o más métodos de la clase no damos su imple-
mentación. Si una clase tiene al menos un método sin implementación la clase es
abstracta y se tiene que indicar eso como se indica a continuación:
6.5 Clases abstractas 322

Sintaxis:
xencabezado de clase abstractay ::= abstract class xidentificadory
Semántica:
Le estamos indicando al compilador dos caracterı́sticas:
No pueden crearse objetos de esta clase.
Contiene al menos un método cuya implementación no está definida.

Aquellos métodos que no se puede definir su implementación en el nivel de la


jerarquı́a en la que está la clase, se les precede también de la palabra abstract y se
escribe únicamente su encabezado seguido de un ;. En el listado 6.17 podemos ver
un ejemplo con la jerarquı́a de clases que dimos antes. No pondremos las clases
completas porque no es el objetivo en este momento.

Código 6.17 Clases abstractas y concretas


/∗ R aı́ z de l a j e r a r q uı́ a . C l a s e a b s t r a c t a ∗/
abstract class FiguraGeometrica {
/∗ E s q u i n a i n f e r i o r i z q u i e r d a ∗/
i n t x1 , y1 ;
abstract public void p i n t a ( ) ;
a b s t r a c t p u b l i c v o i d mueve ( i n t x2 , i n t y2 ) ;
a b s t r a c t p u b l i c v o i d c o p i a ( i n t x2 , i n t y2 ) ;
......
}

/∗ S i g u i e n t e n i v e l de l a j e r a r q uı́ a . También a b s t r a c t a ∗/
a b s t r a c t c l a s s L i n e a extends F i g u r a G e o m e t r i c a {
abstract public void l o n g i t u d ( ) ;
......
}

......
/∗ T e r c e r n i v e l de l a j e r a r q uı́ a . C l a s e c o n c r e t a ∗/
c l a s s R e c t a extends L i n e a {
public void p i n t a ( ) {
...
}
...
}
...
323 Herencia

No hay la obligación de que todos los métodos en una clase abstracta sean
abstractos. Dependerá del diseño y de las posibilidades que tenga la superclase
para definir algunos métodos para aquellas clases que hereden. Aun cuando la
clase no tenga métodos abstractos, si queremos que no se creen objetos de esa
clase la declaramos como abstracta.

6.6 Interfaces
La herencia en Java es simple, esto es, cada clase puede heredar de a lo más
una superclase. Pero muchas veces necesitamos que hereden de más de una clase.
Las interfaces corresponden a un tipo, como las clases, que definen exclusivamente
comportamiento. Lo único que pueden tener las interfaces son constantes estáticas
y métodos abstractos, por lo que definen el contrato que se establece con aquellas
clases que implementen esa interfaz. Se dice que una clase implementa una interfaz,
porque cuando una clase hereda de una interfaz debe dar la implementación de los
métodos declarados en la interfaz. La sintaxis para la declaración de una interfaz,
como ya hemos visto, es:

Sintaxis:
xencabezado de interfazy ::= interface xidentificadory
...
Semántica:
Se declara un tipo que corresponde a constantes y encabezados de métodos.
Como todo lo que se declare dentro de una interfaz es público, pues corresponde
siempre a lo que puede hacer un objeto, no se usa el calificativo public en los
enunciados dentro de la declaración de la interfaz. Como forzosamente todos los
métodos son abstractos, ya que no tienen implementación, tampoco se pone este
calificativo frente a cada método. Y como sólo se permiten constantes estáticas, los
calificativos de static y final se omiten en las declaraciones de constantes dentro
de una interfaz. Por lo tanto, las constantes y métodos en una interfaz serán
declarados nada más con el tipo de la constante o el tipo de valor que regrese el
método, los identificadores y, en el caso de las constantes el valor; en el caso de
los métodos, los parámetros.
Pensemos en el comportamiento de una lista, como las que hemos estado vien-
do. Sabemos que no importa de qué sea la lista, tiene que tener un método que
6.6 Interfaces 324

agregue, uno que busque, etc. Dependiendo de los objetos en la lista y de la imple-
mentación particular, la implementación de cada uno de los métodos puede variar.
Tenemos acá un caso perfecto para una interfaz. En el listado 6.18 podemos ver
la declaración de una interfaz de este tipo.

Código 6.18 Interfaz para manejar una lista


10 interface Lista {
20 void agrega ( Object obj ) ;
30 boolean q u i t a ( S t r i n g cad ) ;
40 O b j e c t b u s c a ( i n t c u a l , S t r i n g cad ) ;
50 String armaRegistro ( ) ;
60 void l i s t a T o d o s ( Scanner cons ) ;
70 v o i d l o s Q u e C a z a n ( i n t c u a l , S t r i n g cad ) ;
80 }

Como se ve en el listado 6.18 ninguno de los métodos tiene su implementación.


Cualquier clase que implemente a esta interfaz tiene que dar la implementación de
todos y cada uno de los métodos dados en esta declaración. La excepción se da si
tenemos una jerarquı́a de clases donde algunas de las clases son abstractas, enton-
ces se podrá listar nada más el encabezado, precedido de la palabra abstract. Por
ejemplo, si deseamos una Lista de EstudianteCalifs podrı́amos tener declaraciones
como las que se ven en el listado 6.19.

Código 6.19 Herencia con una interfaz


10 c l a s s L i s t a C u r s o implements L i s t a {
20 E s t u d i a n t e C a l i f s cabeza ;
30 ...
40 O b j e c t b u s c a ( i n t c u a l , S t r i n g cad ) {
50 ...
60 }
70 ...
80 }

Dado que las interfaces corresponden a tipos, igual que las clases, podemos
declarar variables de estos tipos. Por ejemplo,
Lista miLista ;

y usarse en cualquier lugar en que se pueda usar un objeto de una clase que imple-
menta a la interfaz Lista, de la misma manera que se puede usar una variable de la
clase Object en lugar de cualquier objeto. Sin embargo, si deseamos que el objeto
sea visto como la subclase tendremos que aplicar lo que se conoce como “casting”’,
325 Herencia

que obliga al objeto a comportarse como de la subclase. Se logra poniendo el tipo


al que deseamos conformar al objeto de tipo Object entre paréntesis, precediendo
a la variable:
E s t u d i a n t e C a l i f s nuevo = ( E s t u d i a n t e C a l i f s ) b u s c a ( 1 , " Pedro " ) ;

El método busca nos regresa un objeto de tipo Object (la referencia), pero
sabemos, por la implementación de busca, que en realidad nos va a regresar una
referencia a un objeto de tipo EstudianteCalifs, por lo que podemos aplicar el
casting.
Una clase dada puede extender a una sola superclase, pero puede implementar
a tantas interfaces como queramos. Como no tenemos la implementación de los
métodos en las interfaces, aun cuando una misma firma aparezca en más de una
interfaz (o inclusive en la superclase) la implementación que se va a elegir es la
que aparezca en la clase, por lo que no se presentan conflictos.
Las interfaces pueden extender a una o más interfaces de la misma manera
que subclases implementan a una o más interfaces (como lo que se da en una
interfaz es únicamente firmas y constantes, no habrá conflicto). En caso de que
haya declaradas constantes con el mismo nombre, pero tipos distintos o valores
distintos en más de una interfaz, el compilador exigirá que se discrimine de cuál
de las interfaces se está hablando. Como las interfaces no ejecutan código, más
que en posibles asignaciones a constantes, se debe hacer explı́cito a cuál de los
varios valores nos estamos refiriendo, precediendo el valor con el nombre de la
interfaz –en realidad, siempre que queramos usar alguna constante de una interfaz
tendremos que dar el nombre completo de la constante, que incluye el nombre de
la interfaz–. La sintaxis es la misma que con la extensión de clases, excepto que
permite extender a más de una interfaz.

Sintaxis:
interface xidentif1 y extends xidentif2 y, . . . , xidentifn y
Semántica:
De la misma manera que con las clases, la sub-interfaz hereda todas las
definiciones de los métodos de las súper-interfaces.

En adelante se hará un uso más intenso de herencia y seguiremos, como hasta


ahora, insistiendo en el uso de interfaces.
6. Ejercicios 326

Ejercicios

6.1.- Da un método de Java, usando el enunciado for, que haga lo siguiente:


No recibe parámetros.
Regresa la matriz como resultado.
Declara una matriz en Java que tenga 5 renglones y 3 columnas.
En cada posición de la matriz deberás colocar la suma del número de
renglón con el número de columna.
Debe imprimir la matriz por renglones.
El algoritmo se muestra en el diagrama de Warnier que sigue.
$ !
'
'
' Inicio Construir matriz de n  m
'
'
' $ $
'
'
' ' '
Matriz de n '& '
& '
&
renglones por
Llenar renglón Llenar columna
matriz[i][j] Ð i+j
m columnas ' '
'
(i=0,1,. . . ,n-1) '
'
%
(j=0,1,. . . ,m-1) '
'
%
'
'
'
'
'
' !
'
%Final Regresar matriz construida

6.2.- Escribe un método en Java que, usando el enunciado for, construya una
matriz triangular izquierda donde en cada entrada de la matriz coloque el
número de renglón. El método debe imprimir esta matriz en la consola. El
método recibe como parámetro el número de renglones. El primer renglón
tiene una columna con un 1, el segundo dos con 2, y ası́ sucesivamente. El
algoritmo se muestra en el diagrama de Warnier que sigue.
$ # #
'
'
'
'
'
'
Inicio Construir renglón Construir columna
'
'
'
(k) (i+1)
'
'
' # #
&
Matriz triangular Llenar renglón Llenar columna triangulo[i,j] Ð i+1
con k renglones '' (i=0,. . . ,k) (j=0,. . . ,i)
'
'
' $ $
'
'
' & &Imprimir columna
'
'
' Imprimir renglón (j=0,. . . ,i)
'
%Final % (i =0,. . . ,k-1) %Cambiar de renglón
327 Herencia

6.3.- Construye una matriz triangular de caracteres que recibe como parámetro el
número de columnas en el último renglón (debe ser impar), y al imprimirla
se muestre de la siguiente forma:
a
b b b
c c c c c
d d d d d d d
e e e e e e e e e

En el ejemplo anterior el número de columnas dado como parámetro es 9,


pero tu método debe poder hacerlo con cualquier número impar de columnas.

6.4.- Escribe un método en Java que recibe como parámetros dos arreglos de en-
teros de una dimensión y regresa un arreglo de enteros de una dimensión que
consiste de la concatenación del segundo arreglo a continuación del primero.
El último elemento de cada uno de los arreglos es el valor que corresponde
a un Integer.MIN VALUE, que el el menor valor que puede almacenar una
variable de tipo int. El arreglo nuevo no debe contener ninguna celda vacı́a
ni debe aparecer este número que se usa como centinela.

6.5.- Tenemos un arreglo de una dimensión de enteros que contiene información


de la siguiente manera –éste es un ejemplo–:
Repeticiones
En la posición
del entero i
0 5
1 2
2 3
3 0
4 1
Escribe un método que construya un arreglo de una dimensión donde apa-
rezcan los elementos denotados en la segunda columna. Por ejemplo, para el
arreglo anterior, el método debe construir un arreglo de enteros que tenga
lo siguiente:

0 0 0 0 0 1 1 2 2 2 4

La tabla me dice que deben aparecer cinco ceros, seguidos de dos unos,
seguidos de tres doses, cero treses y por último un cuatro.
6. Ejercicios 328

Se debe obtener primero el número total de números que aparecen; construir


el arreglo correspondiente y acomodar en cada posición el número de que se
trata.
6.6.- Describe la relación de herencia que puede haber entre vehı́culos que trans-
portan a una o más personas por tierra.
6.7.- Describe el árbol de herencia que puede haber entre distintos paquetes de
juegos de la Guerra de las Galaxias –un kit básico trae una nave y un piloto;
conforme aumenta el precio se le van agregando cosas al kit, como estaciones
de servicio, personajes, etc.–.
6.8.- Tenemos la siguiente clase para definir distintas propiedades de cajas.

Código 6.20 Caja.java


10 package H e r e n c i a ;
20 p u b l i c c l a s s C a j a {
30 p r o t e c t e d double ancho ;
40 p r o t e c t e d double a l t o ;
50 p r o t e c t e d double p r o f u n d o ;
60 p u b l i c C a j a ( double ancho , double a l t o , double p r o f u n d o ) {
70 t h i s . ancho = ancho ;
80 this . alto = alto ;
90 this . profundo = profundo ;
100 }
110 p u b l i c double volumen ( ) {
120 r e t u r n ancho ∗ a l t o ∗ p r o f u n d o ;
130 }
140 public String toString () {
150 S t r i n g r e t = " Ancho = " + ancho
160 + "\ nAlto = " + a l t o
170 + "\ nProfundo = " + p r o f u n d o
180 + "\n" ;
190 return r e t ;
200 }
210 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
220 C a j a c a j a = new C a j a ( 3 . 5 , 2 . 8 , 1 . 2 ) ;
230 System . o u t . p r i n t l n ( c a j a + " Volumen = " + c a j a . volumen ( ) ) ;
240 }
250 }

Escribe una subclase PesoCaja de Caja que defina el peso de las cajas, agre-
gando un atributo peso que defina el peso por centı́metro cuadrado de la
caja.
329 Herencia

6.9.- Define una subclase de PesoCaja que calcule el costo de enviar una caja como
el mı́nimo entre un costo base y el costo calculado como el peso de la caja
por un costo por kilogramo que pese la caja.

6.10.- Tenemos la siguiente jerarquı́a de clases:

Figura

Circulo Rectangulo

Cuadrado

Con los siguientes esqueletos para las clases.


Código 6.21 Figura.java
10 package H e r e n c i a ;
20 a b s t r a c t c l a s s F i g u r a {
30 p r i v a t e S t r i n g nombre ;
40
50 p u b l i c F i g u r a ( S t r i n g nombreFig ) {
60 nombre = nombreFig ;
70 }
80
90 a b s t r a c t p u b l i c double a r e a ( ) ;
100
110 f i n a l p u b l i c boolean menorQue ( F i g u r a l a d o D e r e c h o ) {
120 return area () < ladoDerecho . area ( ) ;
130 }
140 f i n a l public String toString () {
150 r e t u r n nombre + " con area " + a r e a ( ) ;
160 }
170
180 }

Código 6.22 Circulo.java


10 package H e r e n c i a ;
20 p u b l i c c l a s s C i r c u l o extends F i g u r a {
30 p r i v a t e double r a d i o ;
40 s t a t i c f i n a l p r i v a t e double PI = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 ;
50
60 }
6. Ejercicios 330

Código 6.23 Rectangulo.java


10 package H e r e n c i a ;
20 p u b l i c c l a s s R e c t a n g u l o extends F i g u r a {
30 p r i v a t e double l a r g o ;
40 p r i v a t e double ancho ;
50
60 }

Código 6.24 Cuadrado.java


10 package H e r e n c i a ;
20 p u b l i c c l a s s Cuadrado extends R e c t a n g u l o {
30
40 }

a) Proporciona los constructores para cada una de las clases que extienden
a Figura.

b) Escribe la implementación para el método area() que calcula el área de


cada figura y que es abstracto en la superclase.

c) Escribe un método main en la clase Circulo que pruebe que todas tus
clases que no son abstractas funcionan bien y calculan bien el área.

6.11.- Tenemos la clase UsoDeFiguras que construye un arreglo de objetos de la


clase abstracta Figuras, una vez completada la definición de las clases de la
jerarquı́a.
331 Herencia

Código 6.25 UsoDeFiguras.java


10 package H e r e n c i a ;
20 import j a v a . u t i l . S c a n n e r ;
30 p u b l i c c l a s s U s o D e F i g u r a s {
40 public s t a t i c Scanner cons ;
50 private static Figura leeFigura () {
60 f i n a l i n t CIRCULO = 1 ,
70 RECTANGULO = 2 ,
80 CUADRADO = 3 ;
90 int opcion = 0;
100 double r a d ;
110 double l a r g o ;
120 double ancho ;
130 do {
140 System . o u t . p r i n t l n ( "Dame el tipo de figura :\n"
150 + "(1) Cı́rculo \n"
160 + "(2) Rectángulo \n"
170 + "(3) Cuadrado \n"
180 + "\n Opción --> " ) ;
6. Ejercicios 332

Código 6.25 UsoDeFiguras.java


190 opcion = cons . n e x t I n t ( ) ;
200 i f ( opcion < 1 | | opcion > 3) {
210 System . o u t . p r i n t l n ( " Opción inválida " ) ;
220 }
230 } while ( opcion < 1 | | opcion > 3 ) ;
240 switch ( opcion ) {
250 case CIRCULO :
260 System . o u t . p r i n t ( "Dame el radio del cı́rculo --> " ) ;
270 rad = cons . nextDouble ( ) ;
280 r e t u r n new C i r c u l o ( r a d ) ;
290 case RECTANGULO :
300 System . o u t . p r i n t ( "Dame el largo y ancho del rectángulo ,\n"
310 + " separados entre sı́ por un blanco --> " ) ;
320 l a r g o = cons . nextDouble ( ) ;
330 ancho = c o n s . n e x t D o u b l e ( ) ;
340 r e t u r n new R e c t a n g u l o ( l a r g o , ancho ) ;
350 case CUADRADO:
360 System . o u t . p r i n t ( "Dame el tama~ n o de cada lado --> " ) ;
370 l a r g o = cons . nextDouble ( ) ;
380 r e t u r n new Cuadrado ( l a r g o ) ;
390 d e f a u l t : r e t u r n new C i r c u l o ( 0 ) ;
400 }
410 }
420
430 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
440 c o n s = new S c a n n e r ( System . i n ) ;
450 System . o u t . p r i n t ( "Da el número de figuras que deseas -->" ) ;
460 i n t num = c o n s . n e x t I n t ( ) ;
470 F i g u r a [ ] a r r e g l o = new F i g u r a [ num ] ;
480 f o r ( i n t i = 0 ; i < num ; i ++) {
490 arreglo [ i ] = leeFigura ();
500 }
510 f o r ( i n t i = 0 ; i < num ; i ++) {
520 System . o u t . p r i n t l n ( " Arreglo [" + i + "]:"
530 + arreglo [ i ]);
540 }
550 }
560 }

Vamos a ordenar las figuras de acuerdo a su área. Para ello usaremos un


método sencillo de ordenamiento, muy ineficiente pero efectivo. Damos va-
rias vueltas al arreglo y en cada vuelta:

Elegimos el elemento menor del arreglo.


Lo copiamos, en orden en un nuevo arreglo.
333 Herencia

Recorremos los elementos del arreglo original para ocupar el lugar del
elemento copiado.
Al terminar (cuando no queden elementos válidos en el arreglo original)
regresamos el arreglo nuevo.
Escribe el método en Java que hace este ordenamiento (puede ser estática).
El diagrama de Warnier para este método se encuentra a continuación.

$ $
'
'
' '
' posMenor Ð 0
'
' '
'
'
'
'
' '
'
' menor Ð primera figura
'
'
' &
'
'
' Inicio Construye un arreglo nuevo
'
'
' '
' del mismo tamaño
' '
'
'
'
' '
'
' lugarEnNuevo Ð 0
'
'
' '
%
'
' hayEnViejo Ð tamaño del arreglo
'
'
' $
'
' $ #
'
'
' '
'
'
'
'
' posMenor Ð j
'
' '
' '
' actual <menor
Ordena & '
' & menor Ð figs[j]
Figuras '
'
' Elige menor
por área '
'
'
'
'
'
' (j=0,.., '
'
' ` !
'
'
' '
& '
'
%
'
'
'
Procesa menor hayEnViejo) actual <menor H
'
' (i=1...núm. de
'
'
' elementos) ' '
' Copia menor a arreglo nuevo
'
' '
'
'
'
'
' '
' Recorre elementos a ocupar lugar
'
' '
'
'
'
'
' '
'
'
' '
'
%
lugarEnNuevo ++
'
'
' hayEnViejo –
'
'
'
'
' "
'
'
%Final Entrega arreglo de
figuras ordenado
6.12.- Agrega la figura Triangulo a la jerarquı́a de clases dada y escribe el código
de Java necesario para que sea tomada en cuenta en UsoDeFiguras.
6.13.- Agrega la figura Rombo a la jerarquı́a de clases dada y escribe el código de
Java necesario para que sea tomada en cuenta en UsoDeFiguras. Se puede
agregar como subclase de rectángulo, pero calculando el área de manera
distinta.
6.14.- Agrega la figura Trapecio a la jerarquı́a de clases dada y escribe el código
de Java necesario para que sea tomada en cuenta en UsoDeFiguras.
Administración de
la memoria durante 7
ejecución

7.1 La pila y el heap

Revisitaremos el tema de asignación de espacio durante la ejecución del pro-


grama, pues hay varios grandes detalles a los que no les hemos dado la importancia
adecuada. Empecemos por lo que sucede en la memoria de la máquina durante
la ejecución del programa y para ello veamos el concepto de la estructura de blo-
ques de Java y qué sucede con ella durante ejecución. La estructura de bloques
sintáctica (estática) tiene la forma que se ve en la figura 7.1 en la siguiente página.
Un archivo de Java puede tener una o más clases declaradas. Hasta ahora hemos
hablado extensamente de los campos que se declaran en la clase y cómo son ac-
cesibles desde cualquier método de la misma clase. También hemos visto el papel
que juegan los parámetros en los métodos de las clases y que corresponde a varia-
bles locales de las mismas. Adicionalmente a los parámetros tenemos las variables
declaradas dentro de un método, a las que únicamente dentro de ese método, al
7.1 La pila y el heap 336

igual que los parámetros, se tiene acceso –de ahı́ viene el nombre de locales–. Adi-
cionalmente a esto, cuando se abren bloques de enunciados en las condicionales o
en las iteraciones, se pueden declarar variables que únicamente son locales dentro
de ese bloque de enunciados. Sólo son conocidas dentro del bloque1 .

Figura 7.1 Estructura de bloques de un programa.


Clase
atributos

método

método
bloque

bloque

método
bloque

método
bloque
bloque

Esto nos define dos niveles lexicográficos distintos: el global, que se refiere a
lo que se puede utilizar desde cualquier punto de cualquiera de nuestras clases,
dados los permisos adecuados, y el local, que es aquello que se encuentra dentro
de un método. Adicionalmente, dentro de los métodos podemos tener bloques
de enunciados que incluyan declaraciones. Estas declaraciones son únicamente
visibles dentro del bloque de instrucciones.
Para los métodos estáticos, como es el caso del método main de las clases,
esto funciona un poco distinto, ya que este tipo de métodos no tiene acceso más
1
Sin embargo, en el caso de una variable declarada en el encabezado de una iteración for, si
existe alguna declaración de una variable con el mismo nombre fuera del bloque y que la precede,
el compilador dará error de sintaxis por identificador ya declarado.
337 Administración de la memoria durante ejecución

que a los atributos o métodos estáticos de la misma clase y a los métodos o


atributos públicos o de paquete de las clases a las que se tenga acceso. Si se trata
de identificadores declarados como estáticos, el acceso es a través del nombre de
la clase o de un objeto de esa clase –xclasey.xidentificadory–, mientras que si se
trata de un atributo o método de objeto (no estático) se tiene que invocar desde
un objeto de esa clase. Olvidándonos un poco de los métodos estáticos (de clase)
podemos decir que la estructura de bloques nos da lo que conocemos como el rango
de una variable, que se refiere a aquellos puntos de la clase donde la variable puede
ser utilizada. Si regresamos a nuestro esquema de los espejos/cristales, tenemos
que desde dentro de un bloque podemos ver hacia afuera: desde el nivel local
tenemos acceso a las variables de la clase –el rango de las variables de la clase es
toda la clase–. Desde dentro de un método, sin embargo, no podemos ver lo que
está declarado dentro de otro método o bloque. Pero ¿qué pasa cuando el nombre
de una variable de clase se repite dentro de un bloque como parámetro o variable
local? En este caso decimos que la variable local bloquea a la variable global: si
existe una variable local con el mismo nombre, todas las referencias a esa variable
en ese bloque se refieren a la variable local. El compilador utiliza a la variable que
le “queda más cerca”, siempre y cuando tenga acceso a ella, la pueda ver.

El rango de una variable está definido estáticamente, pues está dado por la
estructura del programa: de ver el listado podemos decir cuál es el rango de una
variable dada y definirlo como público (global, pero atado a un objeto); local
(declarado dentro de un método) o como privado para una clase. Es el compilador
el que se encarga de resolver todo lo relacionado con el rango de las variables,
siguiendo para esto la estructura de bloques.

Durante la ejecución del programa esta estructura presenta un cierto “anida-


miento”, distinto del anidamiento sintáctico o lexicográfico, donde desde dentro
de un método se puede llamar a cualquiera de los que está en rango, es decir cual-
quiera de los métodos declarados en la clase o que sean públicos de otras clases.
Se lleva a cabo un anidamiento dinámico durante ejecución, donde se establece
una cadena de llamadas tal que el último método que se llamó es el primero que
se va a abandonar.
7.1 La pila y el heap 338

Supongamos que tenemos la clase del listado 7.1.

Código 7.1 Clase que ilustra el anidamiento dinámico Cualquiera (1/2)

10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
...
30 p u b l i c v o i d A( i n t i ) {
...
40 B( i , a ) ;
...
50 }

Código 7.1 Clase que ilustra el anidamiento dinámico Cualquiera (2/2)


...
60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
...
80 C();
...
90 }
100 public void C( ) {
110 int k = 2 ∗ a ;
...
120 }
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 i n t m = 10 , n = 5 ;
150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
160 o b j e t o . A(m) ;
170 o b j e t o . B(m, n ) ;
180 objeto .C( ) ;
190 }
200 }

En el esquema de la figura 7.2 en la página opuesta tenemos los bloques en el


orden en que son invocados, mostrando el anidamiento en las llamadas. El nombre
del método junto con el valor de sus parámetros se encuentra sobre la lı́nea que lo
demarca. En la esquina superior derecha de cada bloque se encuentra el nivel de
anidamiento dinámico que tiene cada método. El valor de las variables, tanto de los
339 Administración de la memoria durante ejecución

atributos como de las variables locales, dependerá del anidamiento lexicográfico.

Figura 7.2 Diagrama de anidamiento dinámico.

main [1]
Constructor de objeto [2]

A(10) [2]
B(10,3) [3]
C( ) [4]

B(3,2) [2]
C( ) [3]

C( ) [2]

Por ejemplo, dentro del método B en el que se le asigna valor a una variable
entera a –lı́nea 70– y que es variable local a B, el valor del atributo a no se va a
ver modificado. Por ello, en la llamada de la lı́nea 170 al método B, los valores
con los que es llamado son los originales de a y b, o sea 3 y 2.
Sigue un diagrama de Warnier en la figura 7.3 que nos muestra la relación
entre las llamadas.

Figura 7.3 Secuencia de$llamadas en el listado 7.1.


'
'
' Construcción de objeto
'
'
' $
'
'
' '
'
'
' '
'
'
... $
'
'
' & &. . .
'
'
' Llamada a A(10) Llamada a B(10,3) Llamada a C()
'
'
& '
'
' %
Secuencia de '
%. . . ...
ejecución '
'
'
'
' $
'
'
' &. . .
'
'
' Llamada a B(3,2)
'
'
' %Llamada a C()
'
'
'
. . .
'
'
' !
%Llamada a C() ...
7.1 La pila y el heap 340

Lo que me indican los dos diagramas anteriores, es que la ejecución del pro-
grama debe proseguir de la siguiente manera:
1. Entrar a ejecutar main.
2. Entrar a ejecutar el constructor de Cualquiera.
2. Salir de ejecutar el constructor de Cualquiera.
2. Entrar a ejecutar el método A(10).
3. Entrar a ejecutar el método B(10,3).
4. Entrar a ejecutar el método C().
4. Salir de ejecutar el método C().
3. Salir ejecutar el método B(10,3).
2. Salir de ejecutar el método A(10).
2. Entrar a ejecutar el método B(3,2).
3. Entrar a ejecutar el método C().
3. Salir de ejecutar el método C().
2. Salir ejecutar el método B(3,2).
2. Entrar a ejecutar el método C().
2. Salir de ejecutar el método C().
1. Salir de ejecutar main.
Tanto en el esquema como en la secuencia de ejecución (donde omitimos para
cada función la ejecución de lo que no fuera llamada a método), asociamos un ente-
ro a cada llamada. Esto es con el objeto de identificar los anidamientos dinámicos
–los que están definidos por la secuencia de ejecución del programa–. Este esque-
ma muestra varios aspectos importantes que tienen que ver con la ejecución de un
programa. Revisemos algunos de ellos:
1. El anidamiento dinámico (en ejecución) no forzosamente coincide con el
estático (sintáctico). Mientras que lexicográficamente hablando únicamen-
te tenemos el nivel global y el local, dinámicamente podemos tener tantos
niveles como queramos, uno por cada vez que desde dentro de una función
llamamos a otra.
2. La última rutina a la que entramos es la primera de la que salimos.
3. Cuando aparece una función f como argumento de una función g, la llamada
a f se inicia y termina antes que la llamada a g. Para poder llamar a g
debemos tener el valor de sus argumentos, por lo que es necesario que antes
de entrar a g obtengamos el valor de f.
4. El nivel dinámico que le corresponde a una función f que aparece como
argumento de una función g es el mismo que el de la función g.

Para poder hacer esto, la ejecución del programa se lleva a cabo en la memo-
ria de la máquina, organizada ésta como una pila –stack en inglés–, que es una
341 Administración de la memoria durante ejecución

estructura de datos con las siguientes caracterı́sticas:

a) Respecto a su estructura:

La estructura es lineal, esto es, podemos pensarla con sus elementos “for-
mados” uno detrás del otro.
Es una estructura homogénea, donde todos sus elementos son del mismo
tipo.
Es una estructura dinámica, esto es, crece y se achica durante ejecución.
Tiene asociado un tope, que corresponde al último elemento que se co-
locó en la pila.

b) Respecto a su uso:

Una pila empieza siempre vacı́a, sin elementos.


Conforme progresa la ejecución, se van colocando elementos en la pila y
se van quitando elementos de la pila, siguiendo siempre esta regla: los
elementos se colocan siempre en el tope de la pila y cuando se remueven,
se hace también del tope de la pila.

Veamos un esquema de una pila en la figura 7.4.


7.1 La pila y el heap 342

Figura 7.4 Esquema de una pila (stack)

tope
... ...
crece ... ...
hacia
... ...
“arriba”
... ...
... ...

stack o pila

El tope de la pila corresponde a un apuntador o referencia que indica cuál es


el siguiente lugar en el que se van a colocar datos en la pila. Suponiendo que la
primera posición a ocupar en una pila es la 0, si el tope vale 0 quiere decir que la
pila está vacı́o.
Para poder ejecutar un programa, el sistema cuenta con un contador de pro-
grama (Program Counter:PC ) que apunta a (contiene la dirección de) la siguiente
instrucción a ejecutarse. El código del programa se encuentra en una sección de
memoria, mientras que las variables y el resultado de la ejecución se colocan en la
pila. Los objetos se encuentran en el heap. El algoritmo para ejecutar un programa
se encuentra en la figura 7.5.
Figura 7.5 Algoritmo para ejecutar un programa.
$ $
'
'
' '
'
' Toma la siguiente instrucción
'
'
' '
'
'
'
'
' '
'
' Obtiene los operandos
'
'
' '
'
'
'
'
' '
'
'
Incrementa el contador$del programa
& Ejecuta instrucciones & '
'
Ejecuta
(Hasta que encuentres '
'
'
Suma
À
programa '
' '
' '
'
&
'
'
' la de “parar”) '
'
'
'
'
' '
'
' Ejecuta la instrucción Resta
'
' '
' '
'
' À
'
'
' '
'
' '
'
'
' '
' '
'
% ...
% %

Antes de empezar a ejecutar un programa, el sistema operativo debe cargar


en la pila de ejecución todo lo que corresponde a lo que está accesible para la
343 Administración de la memoria durante ejecución

clase que se va a ejecutar, que incluye los nombres de las clases accesibles y las
variables y métodos de la clase que se va a ejecutar. A esto le llamamos el paso 0
en la ejecución de un programa.
Cuando se está ejecutando un programa se puede invocar un método desde
distintos puntos del programa. En el punto de llamada de un método la ejecución
debe transferirse a ejecutar ese método y una vez terminada le ejecución del mismo
regresar al punto desde donde se hizo la invocación, para continuar con la ejecución
del programa. A la posición en la que se encuentra la llamada se le conoce como
punto de llamada e indica el punto al que debe regresar la ejecución del programa
una vez que termine la función. Esta última caracterı́stica hace que se le utilice
como dirección de regreso. La ejecución del programa, como ya mencionamos, se
lleva a cabo en la pila. Cada vez que se invoca a un método –el programa principal
main es una función invocada por el sistema operativo– se tiene que “montar” al
método en la pila, anotando muy claramente a dónde debe regresar la ejecución
al terminar la rutina. Al terminar la ejecución del método, se “desmonta” de la
pila al método. Para “montar” un método a la pila hay que construir lo que se
conoce como su registro de activación, que es una tabla en la que hay lugar para
los parámetros y las variables locales del método, de tal manera que durante la
ejecución se encuentren siempre en la parte superior de la pila.
Al invocar un método (para transferirse a ejecutarlo), el sistema debe realizar
los siguientes pasos:
1. Dejar un lugar en la pila para que el método coloque ahı́ el valor que va a
regresar, si es que regresa valor.
2. Hacer la marca en la pila, copiando ahı́ el contenido del contador del pro-
grama, que corresponde al punto de regreso.
3. Buscar en la pila, en el registro de activación global, la dirección de código
donde se encuentra definido ese método. Copiar esa dirección al contador del
programa (es donde va a iniciar la ejecución del método invocado cuando se
termine de montarlo en la pila).
4. Construir el registro de activación del método, dejando un lugar para cada
parámetro, en el orden en que están declarados, y un lugar para cada variable
local (o estructura de datos pública o privada, si se trata de una clase).
5. Evaluar los argumentos, para entregarle al método una lista de valores e
irlos colocando en el registro de activación.
6. Copiar a la pila, en el orden en que aparece, el registro de activación (el
último es el que queda en el tope de la pila).
7. Copiar los valores de los argumentos a los lugares para los parámetros en el
registro de activación.
7.1 La pila y el heap 344

8. Conforme se va ejecutando el método, se van colocando en la pila las varia-


bles y referencias a objetos que se van declarando.
Un método tiene acceso a su bloque local (lo que se encuentra a partir de la
última marca en la pila) y al bloque global (lo que se encuentra en la base o fondo
de la pila y hasta la primera marca en la pila). En la base de la pila se encuentran
los identificadores de las clases a las que se tiene acceso desde la clase en ejecución.
Cuando termina la ejecución del método el control debe regresar al punto de
llamada. La ejecución del método termina cuando se llega a un enunciado de
return o bien se llega al final del bloque que define al método. Antes de continuar
la ejecución en el punto de llamada, el sistema tiene que hacer lo siguiente:
1. Localizar la marca de la pila más cercana al tope, la última que se colocó.
2. Colocar en el contador del programa la dirección de regreso que se encuentra
en esa marca y sumarle 1 (para tomar el siguiente enunciado).
3. Si el enunciado que causa la terminación de la rutina es un return xvalory,
colocar el xvalory en el lugar inmediatamente abajo de la marca de la pila
correspondiente.
4. Quitar de la pila todo lo que se encuentra a partir de la marca, incluyéndola.
Se quita algo de la pila simplemente “bajando” el apuntador al tope de la
pila a que apunte al último registro que se quitó (recordar que lo último
que se colocó es lo primero que se quita). No hay necesidad de borrar la
información pues la ejecución solo va a tomar en cuenta aquella información
que se encuentre antes del tope de la pila.
5. Continúa la ejecución en el lugar del código al que apunta el contador del
programa.
Para ilustrar estos pasos, vamos a seguir el programa que escribimos y que tiene
los renglones numerados. Por supuesto que la ejecución del programa no se lleva
a cabo directamente sobre el texto fuente. El compilador y ligador del programa
producen un programa en binario (lenguaje de máquina) que se coloca en un cierto
segmento de la memoria. El contador del programa va apuntando a direcciones
de este segmento de memoria y en cada momento apunta a una instrucción de
máquina. Es suficiente para nuestros propósitos manejar el programa a nivel de
enunciado. En los esquemas de la pila que presentamos a continuación, lo que
corresponde a valores en la pila aparecen tal cual, lo que corresponde a direcciones
en el heap se preceden con una “@” y lo que corresponde a posiciones de código
del programa se precede con un “#” –manejaremos el número del enunciado–. El
contador del programa apunta a la siguiente instrucción a ejecutarse. El tope de
la pila apunta al primer lugar vacı́o en la pila (si el tope de la pila contiene un 0,
quiere decir que no hay nadie en la pila).
345 Administración de la memoria durante ejecución

Al iniciarse la ejecución de una clase se cargan a la pila todos los atributos y


nombres de métodos de la clase2 , quedando la pila como se observa en la figura 7.6.

Figura 7.6 Estado de la pila al iniciarse la ejecución de una clase


10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
30 p u b l i c v o i d A( i n t i ) {
40 B( i , a ) ;
50 }
60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
80 C();
90 }
100 public void C( ) {
xvoidymain #130 110 int k = 2 ∗ a ;
xvoidy C #100 120 }
xvoidy B #60 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xvoidy A #30 140 i n t m = 10 , n = 5 ;
150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xinty b 2 160 o b j e t o . A(m) ;
xinty a 3 170 o b j e t o . B(m, n ) ;
d.r. Sistema 180 objeto .C( ) ;
Operativo 190 }
clase Cualquiera 200 }

El sistema operativo sabe que el primer método a ejecutarse es main, por lo


que inicia la ejecución con él. Sigamos los pasos, uno a uno, para ver como lo hace:

1. Como main no entrega valor, no deja espacio en la pila.

2. Marca la pila para poder montar al método.

3. Localiza la dirección de main en la pila, y ve que es la dirección de código


130. La pila y los contadores quedan como se ve en la figura 7.6.

4. Construye el registro de activación para main. El registro de activación cons-


truido se puede ver en la figura 7.7 en la siguiente página. En el registro se
va dando lugar para cada una de las declaraciones locales. En el caso de
declaraciones de objetos, se colocan en la pila las referencias a los objetos
que se van a localizar en el heap.
2
No ilustraremos las clases a las que tiene acceso para ahorrar espacio y porque serı́a prácti-
camente interminable.
7.1 La pila y el heap 346

Figura 7.7 Registro de activación para main


Heap
xCualquieray objeto @heap a b
3 2
xinty n 5
xinty m 10
xString r sy args @heap

5 y 6 Se monta el registro de activación en la pila. La pila queda como se ve en


la figura 7.8.

Figura 7.8 La pila listo para iniciar la ejecución de main.

10 c l a s s C u a l q u i e r a {
xCualquieray objeto @heap (a=3,b=2) 20 private int a = 3 , b = 2;
xinty n *5 30 p u b l i c v o i d A( i n t i ) {
xinty m 40 B( i , a ) ;
*10
50 }
xString r sy args @heap (?) 60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
d.r. Sistema 80 C();
Operativo 90 }
main 100 public void C( ) {
xvoidy main #130 110 int k = 2 ∗ a ;
xvoidy C #100 120 }
xvoidy B #60 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 i n t m = 10 , n = 5 ;
xvoidy A #30 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xinty b 2 160 o b j e t o . A(m) ;
xinty a 3 170 o b j e t o . B(m, n ) ;
180 objeto .C( ) ;
d.r. Sistema 190 }
Operativo 200 }
clase Cualquiera

Una vez armada la pila, se procede a ejecutar la rutina. En este momento


es accesible todo lo que corresponde a las variables y métodos públicos de
las clases a las que se tiene acceso, a través de los objetos construidos, y lo
que está desde la última marca hasta el tope de la pila.
7. Empieza la ejecución en el enunciado #130, con las declaraciones locales de
main ya montadas en la pila.
Las lı́neas de código 140 y 150 corresponden a las declaraciones que ya hici-
mos, ası́ que procedemos a ejecutar la lı́nea 160. Para ello debemos invocar
347 Administración de la memoria durante ejecución

el método A del objeto objeto. Volvamos a seguir la ejecución, en lo que se


refiere al manejo de la pila.
1 a 3: Como el método no regresa valor, no dejamos un lugar en la pi-
la. Ponemos la marca, colocando en ella la dirección de código que se
encuentre en el contador del programa. Asimismo, se coloca en el con-
tador del programa la dirección del método. Todo esto se puede ver en
la figura 7.9.
Figura 7.9 La pila durante la ejecución de main.
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
dirección de
regreso: #160 30 p u b l i c v o i d A( i n t i ) {
A(10)
40 B( i , a ) ;
xCualquieray objeto @heap (?) 50 }
xinty n
60 p u b l i c v o i d B( i n t i , i n t j ) {
5
70 int a = i + j ;
xinty m 10 80 C();
xString r sy args @heap (a=3,b=2) 90 }
100 public void C( ) {
d.r. Sistema 110 int k = 2 ∗ a ;
Operativo 120 }
main 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }

4 a 6 Al evaluar los argumentos, tenemos la lista (10). Montamos en la pila


el registro de activación y colocamos los valores de la lista en el espacio
reservado para los argumentos. El contenido de la pila en este momento
se puede ver en la figura 7.10.
Figura 7.10 La pila durante la ejecución de A.
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty i 10 30 p u b l i c v o i d A( i n t i ) {
dirección de 40 B( i , a ) ;
regreso: #160 50 }
A(10) 60 p u b l i c v o i d B( i n t i , i n t j ) {
xCualquieray objeto @heap (a=3,b=2) 70 int a = i + j ;
xinty n 5 80 C();
xinty m 10 90 }
100 public void C( ) {
xString r sy args @heap (?) 110 int k = 2 ∗ a ;
d.r. Sistema 120 }
Operativo 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
main ...
200 }
7.1 La pila y el heap 348

8. Continuar la ejecución del programa en la lı́nea de código #40, en la que


tenemos una llamada al método B(i, a), por lo que nuevamente marcamos
la pila, copiamos la dirección del PC a la marca, armamos el registro de
activación para B y lo montamos en la pila, colocamos la dirección donde
empieza B a ejecutarse en el PC y proseguimos la ejecución en ese punto.
En el momento inmediato anterior a que se ejecute B, la pila se presenta
como se puede observar en la figura 7.11.

Figura 7.11 La pila antes de empezar a ejecutar C (dentro de B)

xinty a 13 10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty j 3 30 p u b l i c v o i d A( i n t i ) {
xinty i 10 40 B( i , a ) ;
dirección de 50 }
regreso: #40 60 p u b l i c v o i d B( i n t i , i n t j ) {
B(10,3) 70 int a = i + j ;
xinty i 10 80 C();
dirección de 90 }
regreso: #160
A(10) 100 public void C( ) {
xCualquieray objeto @heap (a=3,b=2) 110 int k = 2 ∗ a ;
120 }
xinty n 5 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xinty m 10 ...
200 }

Al llegar a la lı́nea de código #80 hay una llamada desde B al método C, por
lo que nuevamente se marca la pila, se actualiza el contador del programa
y se monta en la pila el registro de activación de C(). El resultado de estas
acciones se pueden ver en la figura 7.12.

Figura 7.12 La pila antes de empezar a ejecutar C desde la lı́nea #80.


10 c l a s s C u a l q u i e r a {
xinty k 6 20 private int a = 3 , b = 2;
dirección de ...
regreso: #80 90 }
C() 100 p u b l i c v o i d C ( ) {
xinty a 13
110 int k = 2 ∗ a ;
xinty j 3
120 }
xinty i 10
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
dirección de ...
regreso: #40
B(10,3) 200 }
xinty i 10
349 Administración de la memoria durante ejecución

Figura 7.13 La pila al terminar de ejecutarse C().

10 c l a s s C u a l q u i e r a {
xintya 13 20 private int a = 3 , b = 2;
xinty j 3 ...
xinty i 10 60 p u b l i c v o i d B( i n t i , i n t j ) {
dirección de 70 int a = i + j ;
regreso: #40 80 C();
B(10,3) 90 }
xinty i 10
100 public void C( ) {
dirección de 110 int k = 2 ∗ a ;
regreso: #160
A(10) 120 }
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xCualquieray objeto @heap (a=3,b=2) ...
200 }

Se termina la ejecución de B(10,3) en la lı́nea #90, por lo que se desmonta el


registro de activación de B(10,3) de la pila, se copia la dirección de regreso
de la marca al PC y se quita la marca de la pila, quedando la pila como se
muestra en la figura 7.14.

Figura 7.14 La pila al terminar la ejecución de B(10,3).


10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty i 10 30 p u b l i c v o i d A( i n t i ) {
dirección de 40 B( i , a ) ;
regreso: #160
A(10) 50 }
xCualquieray objeto @heap (a=3,b=2) ...
xinty n 5 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xinty m 10 140 i n t m = 10 , n = 5 ;
150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xString[ ]y args @heap (?)
160 o b j e t o . A(m) ;
d. r. Sistema 170 o b j e t o . B(m, n ) ;
Operativo 180 objeto .C( ) ;
main
190 }
200 }

Se llega al final del método A(10), por lo que se desmonta el registro de ac-
tivación de A(10), se copia la dirección de regreso de la marca al contador del
programa y quita la marca de la pila. Podemos observar el estado de la pila en
este momento en la figura 7.15.
7.1 La pila y el heap 350

Figura 7.15 La pila al terminar la ejecución de A(10).


10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xCualquieray objeto @heap (a=3,b=2) ...
xinty n 5 60 p u b l i c v o i d B( i n t i , i n t j ) {
xinty m 10 70 int a = i + j ;
xString r sy args @heap (?) 80 C();
d.r. Sistema 90 }
Operativo 100 public void C( ) {
main ...
xvoidy main #130 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xvoidy C #100 140 i n t m = 10 , n = 5 ;
xvoidy B #60 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xvoidy A #30 160 o b j e t o . A(m) ;
xinty b 2 170 o b j e t o . B(m, n ) ;
xinty a 3
180 objeto .C( ) ;
d.r. Sistema 190 }
Operativo
clase Cualquiera 200 }

Al llegar la ejecución del programa a la lı́nea 170 se encuentra con otra invo-
cación a B(3,2), que son los campos de la clase. Se coloca la marca en la pila con
dirección de regreso 180, se actualiza el PC para que marque el inicio del método
B y se monta a la pila el registro de activación de B(3,2). Los resultados de estas
acciones se muestran en la figura 7.16.

Figura 7.16 La pila en la ejecución de B(10,5).

10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xintya 15 30 p u b l i c v o i d A( i n t i ) {
xinty j 5 40 B( i , a ) ;
xinty i 10 50 }
dirección de 60 p u b l i c v o i d B( i n t i , i n t j ) {
regreso: #170 70 int a = i + j ;
B(10,5) 80 C();
xCualquieray objeto @heap (a=3,b=2) 90 }
xinty n 5 100 public void C( ) {
110 int k = 2 ∗ a ;
xinty m 10 120 }
xString r sy args @heap (?) 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }
351 Administración de la memoria durante ejecución

Al ejecutar al método B(10,5), en la lı́nea 80 se invoca al método C(), por lo


que el sistema hace lo conducente con la pila, quedando éste como se muestra en
la figura 7.17.

Figura 7.17 La pila al entrar a ejecutar C().

10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty k 6 30 p u b l i c v o i d A( i n t i ) {
dirección de 40 B( i , a ) ;
regreso: #80 50 }
C() 60 p u b l i c v o i d B( i n t i , i n t j ) {
xintya 15 70 int a = i + j ;
xinty j 5 80 C();
xinty i 10 90 }
100 public void C( ) {
dirección de
regreso: #170 110 int k = 2 ∗ a ;
B(10,5) 120 }
xCualquieray objeto @heap (a=3,b=2) 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }

Termina de ejecutarse C() en la lı́nea 90 y la pila regresa a verse como en la


figura 7.16 en la página anterior, excepto que el PC vale ahora 7.1. Esta situación
se muestra en la figura 7.18.

Figura 7.18 La pila al terminar la ejecución de C().


10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
30 p u b l i c v o i d A( i n t i ) {
xintya 15 40 B( i , a ) ;
50 }
xinty j 5 60 p u b l i c v o i d B( i n t i , i n t j ) {
xinty i 10 70 int a = i + j ;
dirección de 80 C();
regreso: #170 90 }...
B(10,5)
xCualquieray objeto @heap (a=3,b=2) 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }

Al continuar la ejecución el programa, llega al final del método B(10,5) y sale


de él, dejando la pila como se ve en la figura 7.19, con el PC apuntando a la
dirección de código 180.
7.1 La pila y el heap 352

Figura 7.19 La pila al terminar la ejecución de B(10,5).

60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
80 C();
90 }
100 public void C( ) {
110 int k = 2 ∗ a ;
xCualquieray objeto @heap (a=3,b=2) 120 }
xintyn 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
5 140 i n t m = 10 , n = 5 ;
xinty m 10 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xString[ ] y args @heap (?) 160 o b j e t o . A(m) ;
d. r. Sistema 170 o b j e t o . B(m, n ) ;
Operativo 180 objeto .C( ) ;
main
190 }
200 }

En la lı́nea 180 nuevamente se hace una llamada al método C(), por lo que se
marca la pila y se monta su registro de activación. El resultado se puede ver en la
figura 7.20 en la página opuesta.

Figura 7.20 La pila antes de empezar la ejecución de C().

60 public void B( i n t i , i n t j ) {
xinty k 6 70 int a = i + j;
dirección de 80 C();
regreso: #180 90 }
C() 100 public void C() {
xCualquieray objeto @heap (a=3,b=2) 110 int k = 2 ∗ a;
xinty n 5 120 }
xinty m 10 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 i n t m = 10 , n = 5 ;
xString r sy args @heap (?) 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
d.r. Sistema 160 o b j e t o . A(m) ;
Operativo 170 o b j e t o . B(m, n ) ;
main 180 objeto .C( ) ;
xvoidy main #130 190 }
200 }

Al terminarse de ejecutar C() se desmonta su registro de activación de la pila,


se copia la dirección de regreso de la marca al PC y se quita la marca. la pila
353 Administración de la memoria durante ejecución

queda como se muestra en la figura 7.21, con el PC apuntando a la lı́nea 190 del
código.

Figura 7.21 La pila lista para terminar la ejecución de main.

10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
30 p u b l i c v o i d A( i n t i ) {
40 B( i , a ) ;
50 }
60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
xCualquieray objeto @heap (a=3,b=2) 80 C();
xinty n 5 90 }
100 public void C( ) {
xinty m 10 110 int k = 2 ∗ a ;
xString r sy args @heap (?) 120 }
d.r. Sistema 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Operativo 140 i n t m = 10 , n = 5 ;
main 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xvoidy main #130 160 o b j e t o . A(m) ;
170 o b j e t o . B(m, n ) ;
180 objeto .C( ) ;
190 }
200 }

Como la lı́nea 190 es la que termina main, se descarga de la pila el registro de


activación de este método, se copia al PC la dirección de regreso de la marca y se
quita la marca. En ese momento termina la ejecución del programa, por lo que se
libera la pila y el PC.
En todo momento durante la ejecución, el sistema puede utilizar lo que se
encuentre en el bloque global, más aquello que se encuentre desde la celda inme-
diatamente abajo de la última marca que se colocó –si es que el método regresa
algún valor, ahı́ lo va a colocar– hasta la celda que se encuentre en el tope de la
pila. De esta manera, cada método “crea” su propio ambiente de ejecución.
Resumiendo, la pila se utiliza para la administración de la memoria en eje-
cución. Cada vez que se invoca una rutina o método, se construye el registro de
activación de la misma y se coloca en la pila. Cada vez que se sale de un método,
se quita de la pila el registro de activación de la rutina que está en el tope y la
ejecución continúa en la dirección de regreso desde la que se invocó a ese ejemplar
– instance– del método.
Durante la ejecución de un programa el sistema trabaja con dos variables, el
tope de la pila, que indica cuál es la siguiente celda en la que se va a colocar in-
formación, y el contador del programa, que indica cuál es la siguiente instrucción
que se va a ejecutar. En ambos casos decimos que las variables son apuntadores
7.1 La pila y el heap 354

(o referencias), pues el tope de la pila apunta a una celda en la pila (contiene


una dirección de la pila) y el contador del programa apunta a una dirección de
memoria del programa donde se encuentra almacenado el código del programa.

En la pila se le da lugar a:
Todo lo declarado público en el paquete (o conjunto de programas).
Todas las estructuras de datos de las clases (referencias a ellas).
Apuntadores a todos los métodos miembros de clases.
Los resultados que entregan las funciones.
Los argumentos (parámetros reales) de cada método.
Las variables locales de cada método conforme se van declarando.

Decimos que cada método tiene su ambiente propio de trabajo en la medida


en que al cargarse su registro de activación en la pila, su entorno lo constituye
ese registro de activación y el registro de activación global. En nuestros esquemas
las celdas intermedias entre la primera y última marca no son accesibles en ese
momento de la ejecución. Esto nos da dos conceptos importantes en programación:

Rango de un identificador. Se refiere a los puntos del programa desde donde


el identificador puede ser referido. El rango está dado de manera estática
por la estructura de bloques del programa. El compilador se encarga de que
las referencias a los identificadores sean válidas.

Existencia o vida de una variable: Se refiere a los momentos durante la eje-


cución en que una variable conserva su valor. Una variable declarada existe
mientras se encuentre en la pila. Deja de existir cuando se quita de la pila
el registro de activación que la contiene.

Como ya mencionamos antes, de existir identificadores duplicados el compila-


dor busca a la declaración más cercana en la pila, pero busca únicamente en los
registros de activación vivos (despiertos), el global y el local, primero en el local.
Por ello, al declarar una variable local repitiendo un nombre global se crea un
ejemplar fresco y nuevo, que no tiene relación alguna con la variable global ori-
ginal y que, de hecho, oculta a la variable global original. Java permite ver datos
miembros de una clase que han sido ocultados por declaraciones locales utilizando
el identificador de objeto this seguido del operador “.” y a continuación el nombre
del atributo –ya que los atributos de una clase pertenecen a un ambiente global
de ejecución– pero no existe ningún mecanismo para acceder a variables locales
de métodos que hayan intervenido en la secuencia de llamadas. Debo insistir en
355 Administración de la memoria durante ejecución

que el bloque o registro de activación en el que se encuentra la variable debe ser


visible desde el punto de ejecución y únicamente se aplica a variables que hayan
sido ocultadas por una reutilización del nombre. Si la variable se encuentra en un
registro de activación inaccesible, entonces el compilador emitirá un mensaje de
error.

7.2 Recursividad

En matemáticas nos encontramos frecuentemente con definiciones o funciones


recursivas. El ejemplo tı́pico de este tipo de funciones es la definición del método
factorial o de la serie de Fibonacci:
$ $
'
'
&1 si n 1 '
'
&1 si n ¤2
n!  F ibn 
'
'
% '
'
%
n  pn  1q! si n ¡1 F ibn1 F ibn2 si n ¡2
Si escribimos un método en Java que refleje esta definición de factorial, por
ejemplo, tendrı́amos la implementación que se muestra en el listado 7.2.

Código 7.2 Codificación en Java de la función factorial


long f a c t o r i a l ( i n t n ) {
i f ( n <= 1 ) {
return 1;
}
else {
r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
}
}

La ejecución del método es como sigue: en la invocación desde “fuera” (el


método main o algún otro método) se llama con varios valores para n separados
entre sı́ por uno o más blancos, donde cada uno de ellos debe ser un valor entero.
Se invoca al método factorial con cada uno de estos valores –que se encuentran en
el arreglo de cadenas args que se le proporciona a main–. Si el entero es mayor que
1 procede el método a llamarse nuevamente a sı́ mismo, pero disminuyendo en 1
al argumento. Si el argumento vale 1 el método logra salir. Veamos la ejecución de
7.2 Recursividad 356

este método dentro de una clase en el listado 7.3 y observemos el comportamiento


de la pila durante su ejecución.

Código 7.3 La función factorial


10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
30 i f ( n <= 1 ) {
40 return 1;
50 }
60 else {
70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
80 }
90 }
100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
120 int n = Integer . parseInt ( args [ i ] ) ;
130 i f ( n <= 0 ) {
140 System . o u t . p r i n t l n ( " Factorial no puede tomar un "
150 + " argumento negativo " ) ;
160 continue ;
170 }
180 System . o u t . p r i n t l n ( n + "! es="+ f a c t o r i a l ( n ) ) ;
190 }
200 }
210 }

La pila, una vez que se cargó el registro de activación de main, se muestra en


la figura 7.22 (usaremos la misma notación para anotar a los elementos en la pila
que usamos en el ejemplo anterior).
Numeramos las lı́neas del programa para poder hacer referencia a ellas en la
ejecución, que empieza en la lı́nea 100. En las lı́neas 110 y 120 tenemos las declara-
ciones e inicializaciones de variables locales a main. Entramos a una iteración que
va a usar el arreglo args que usamos en la invocación de Factorial. En la lı́nea 110,
una vez verificado que el argumento es válido, está la llamada a factorial desde
main, donde se pide imprimir el resultado. La primera llamada de factorial desde
main deja la pila como se ve en la figura 7.23 en la página opuesta
Se ejecuta la condicional de la lı́nea 30 pero como n es mayor que 1 no se
hace nada. Después se evalúa la condicional de la lı́nea 60 y como es verdadera se
ejecuta el enunciado en las lı́neas 70 y 80, que es una llamada recursiva a factorial.
Vuelve a entrar a ejecutar factorial y la pila se ve como en la figura 7.24 en la
página 358.
357 Administración de la memoria durante ejecución

Figura 7.22 Estado de la pila al iniciarse la ejecución de una clase.

10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
30 i f ( n <= 1 ) {
40 return 1;
50 }
60 else {
70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
80 }
90 }
100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
d.r. Sistema
Operativo 120 int n = Integer . parseInt ( args [ i ] ) ;
main [4] 130 i f ( n <= 0 ) {
xvoidy main #100 140 System . o u t . p r i n t l n ( " Factorial no puede "
xlongy factorial #20 150 + " tomar un argumento negativo " ) ;
d.r. Sistema 160 continue ;
Operativo 170 }
clase Factorial 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
190 }
200 }
210 }

Figura 7.23 Estado de la pila al iniciarse la llamada de factorial(4) desde main.

10 c l a s s F a c t o r i a l {
xinty n 4 20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
dirección de 30 i f ( n <= 1 ) {
regreso #110 40 return 1;
factorial(4)
xlongy val regreso
50 }
60 else {
xinty n 4 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
xinty i 0
80
90 }
}
d.r. Sistema 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Operativo ...
main [4] 210 }
xvoidy main #100
xlongy factorial #20
d.r. Sistema
Operativo
clase Factorial
7.2 Recursividad 358

Figura 7.24 Estado de la pila al iniciarse la llamada de factorial(3) desde factorial(4).

10 c l a s s F a c t o r i a l {
xinty n 3
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n )
30 i f ( n <= 1 ) {
{
dirección de 40 return 1;
regreso #70 50 }
factorial(3)
xlongy val regreso
60 e l s e {
70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
xinty n 4
dirección de 80 }
regreso #110 90 }
factorial(4)
xlongy val regreso 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
xinty n 4 120 int n = Integer . parseInt ( args [ i ] ) ;
xinty i 0
130
140
i f ( n <= 0 ) {
System . o u t . p r i n t l n ( " Factorial no puede "
d.r. Sistema 150 + " tomar un argumento negativo " ) ;
Operativo 160 continue ;
main [4]
xvoidy main #100
170
180
}
System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
xlongy factorial #20 190 }
d.r. Sistema 200 }
Operativo 210 }
clase Factorial

Nuevamente se evalúa a falso la primera condición y como se evalúa a verdadero


la condición en la lı́nea 60 volvemos a llamar a factorial desde la lı́nea 70, quedando
la pila como se puede apreciar en la figura 7.25 en la página opuesta.
Como n sigue siendo mayor que 1 volvemos a llamar a factorial con 1 como
argumento. la pila se ve como se muestra en la figura 7.26 en la página opuesta.
En esta llamada las condicionales de las lı́neas 30 y 60 ambas se evalúan a
falso, por lo que se ejecuta el enunciado de la lı́nea 140, y se regresa el valor 1.
Esto se traduce en colocar en el espacio reservado para ello cerca del tope de la
pila ese valor, quitar del tope de la pila el registro de activación de la llamada de
factorial(1) y continuar la ejecución en la lı́nea 80 para hacer la multiplicación. la
pila se ve como se muestra en la figura 7.27 en la página 360.
En este punto se puede terminar de ejecutar la invocación de factorial(2), por lo
que nuevamente se hace la multiplicación y se coloca el resultado inmediatamente
abajo de la última marca en la pila; se procede a desmontar el registro de activación
de factorial(2). la pila se muestra en la figura 7.28 en la página 360.
359 Administración de la memoria durante ejecución

Figura 7.25 Estado de la pila al iniciarse la llamada de factorial(2) desde factorial(3).

xinty n 2
dirección de 10 c l a s s F a c t o r i a l {
regreso #70 20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
factorial(2) 30 i f ( n <= 1 ) {
xlongy val regreso 40 return 1;
xinty n 3
50 }
60 e l s e {
dirección de
regreso #70 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
factorial(3) 80 }
xlongy val regreso 90 }
xinty n 4 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
dirección de 210 }
regreso #110
factorial(4)
xlongy val regreso

Figura 7.26 Estado de la pila al iniciarse la llamada de factorial(1) desde factorial(2)

10 c l a s s F a c t o r i a l {
xinty n 1
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n )
30 i f ( n <= 1 ) {
{
dirección de 40 return 1;
regreso #70
factorial(1) 50 }
xlongy val regreso 60 e l s e {
r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ; \
xinty n 2
70
80 }
dirección de 90 }
regreso #70 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
factorial(2)
xlongy val regreso 110
120
f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
int n = Integer . parseInt ( args [ i ] ) ;
xinty n 3 130 i f ( n <= 0 ) {
dirección de 140 System . o u t . p r i n t l n ( " Factorial no puede "
regreso #70 150 + " tomar un argumento negativo " ) ;
factorial(3) 160 continue ;
xlongy val regreso 170 }
xinty n 4 180
190 }
System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
dirección de 200 }
regreso #110
factorial(4) 210 }
7.2 Recursividad 360

Figura 7.27 Estado de la pila al terminarse la llamada de factorial(1).

10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
xlongy val regreso 1
30
40
i f ( n <= 1 ) {
return 1;
xinty n 2 50 }
dirección de 60 e l s e {
regreso #70 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
factorial(2)
xlongy val regreso 80 }

xinty n
90 }
3 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
dirección de 110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
regreso #70 120 int n = Integer . parseInt ( args [ i ] ) ;
factorial(3) 130 i f ( n <= 0 ) {
xlongy val regreso 140 System . o u t . p r i n t l n ( " Factorial no puede "
xinty n 4 150
160
+ " tomar un argumento negativo " ) ;
continue ;
dirección de
regreso #110 170 }
factorial(4) 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
190 }
200 }
210 }

Figura 7.28 Estado de la pila al terminarse la llamada de factorial(2).


10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
xlongy val regreso 122 30 i f ( n <= 1 ) {
xinty n 3
40
50 }
return 1;
dirección de 60 e l s e {
regreso #70
factorial(3) 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
xlongy val regreso 80 }
xinty n 4 90 }
100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
dirección de 110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
regreso #110
factorial(4) 120 int n = Integer . parseInt ( args [ i ] ) ;
xlongy val regreso 130 i f ( n <= 0 ) {

xinty n
140 System . o u t . p r i n t l n ( " Factorial no puede "
4 150 + " tomar un argumento negativo " ) ;
xinty i 0 160
170 }
continue ;
d.r. Sistema
Operativo 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
main [4] 190 }
200 }
210 }
361 Administración de la memoria durante ejecución

Se sale de la llamada de factorial(3), acomodando el resultado que se obtiene


de multiplicar el resultado entregado por factorial(2) y el argumento con valor 3.
la pila se puede ver en la figura 7.29.

Figura 7.29 Estado de la pila al terminarse la llamada de factorial(3).

xlongy val regreso 326


xinty n 4
10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
dirección de 30 i f ( n <= 1 ) {
regreso #110 40 return 1;
factorial(4) 50 }
xlongy val regreso 60 e l s e {
xinty n 4 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
xinty i 0 80 }
d.r. Sistema 90 }
Operativo 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
main [4] ...
xvoidy main #100 210 }
xlongy factorial #20
d.r. Sistema
Operativo
clase Factorial

Nuevamente, al regresar con el valor de factorial(3) en la lı́nea 70, lo multiplica


por 4, usando para ello el valor que colocó la ejecución de factorial(3) en la pila.
Después de quitar el registro de activación y la marca de factorial(3) la pila se ve
como se muestra en la figura 7.30.

Figura 7.30 Estado de la pila al terminarse la llamada de factorial desde main.

10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
30 i f ( n <= 1 ) {
xlongy val regreso 6  4  24 40 return 1;
xinty n 4
50 }

xinty i
60 e l s e {
0 70 r e t u r n ( n∗ f a c t o r i a l ( n  1 ) ) ;
d.r. Sistema 80 }
Operativo 90 }
main [4] 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xvoidy main #100 ...
xlongy factorial #20 110 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
d.r. Sistema 120 }
Operativo 130 }
clase Factorial 140 }
7.2 Recursividad 362

En este momento se procede a escribir el valor de factorial(4) que fue entregado


y colocado en la pila. Se termina de ejecutar el programa, y de manera similar a
métodos no recursivos, se libera la pila y el contador del programa.
Debemos insistir en que usamos la función factorial para mostrar la situación
en la pila porque es fácil de mostrar los cambios que va sufriendo la misma, no
porque sea un buen ejemplo para la recursividad. De hecho, dado que ya vimos el
trabajo escondido que tenemos con la recursividad, una forma más económica de
calcular factorial es con una iteración simple que no involucra manejo de la pila.
El método quedarı́a codificado como se ve en el listado 7.4.

Código 7.4 Factorial calculado iterativamente


s t a t i c p u b l i c long f a c t o r i a l ( i n t n ) {
long f a c t = 1 ;
f o r ( i n t i = 2 ; i <= n ; i ++) {
fact = fact ∗ i ;
}
return f a c t ;
}

7.2.1. Las torres de Hanoi

Un ejemplo donde se entiende muy bien la utilidad de la recursividad es en el


de las torres de Hanoi. El juego consiste de lo siguiente:

El juego consiste de una tabla con tres postes pegados perpendiculares


a la tabla y n discos de radios distintos entre sı́ y con un orificio en el
centro para poder ser colocados en los postes. Al empezar el juego se
encuentran los n discos en un mismo poste, acomodados por tamaño
decreciente, el más grande hasta abajo.
El juego consiste en pasar los n discos a un segundo palo, moviendo
disco por disco; cuando se mueve un disco de un poste a otro está prohi-
bido que quede un disco encima de otro que es menor que él.

En la figura 7.31 se muestra un ejemplo con 8 fichas.


Este juego tiene su origen en un monasterio tibetano y consistı́a de 64 fichas.
La leyenda decı́a que cuando se lograran mover las 64 fichas siguiendo las reglas el
mundo se iba a terminar. El algoritmo para lograr mover las n fichas se muestra
en la figura 7.32.
363 Administración de la memoria durante ejecución

Figura 7.31 Juego de las torres de Hanoi

Figura 7.32 Estrategia recursiva para las torres de Hanoi


$ $
'
' '
'
&Mover 1 ficha del poste 1 al 3
'
'
'
'
' si n  2 Mover 1 ficha del poste 1 al 2
'
'
' '
'
'
' %Mover 1 ficha del poste 3 al 2
Mover n fichas &
del poste 1 al poste 2 $
'
'
'
' &Mover n  1 fichas del poste 1 al 3
'
'
usando el poste 3 '
'
'
'
'
'
' si n ¡ 2 Mover 1 ficha del poste 1 al 2
'
% '
'
% Mover n  1 fichas del poste 3 al 2

Lo que me dice esta estrategia es que si sólo tenemos dos fichas las sabemos
mover “a pie”. Para el caso de que tenga más de dos fichas (n ¡ 2), suponemos
que pudimos mover las n  1 fichas que están en el tope del poste al poste
auxiliar, siguiendo las reglas del juego; después movimos una sola ficha al poste
definitivo, y para terminar movimos las n  1 fichas del poste auxiliar al definitivo.
Como en el caso del cálculo de factorial con recursividad, se entra al método
decrementando la n en 1 hasta que tengamos que mover una sola ficha; en cuanto
7.2 Recursividad 364

la movemos, pasamos a trabajar con el resto de las fichas. Hay que aclarar que
esto funciona porque se van intercambiando los postes 1, 2 y 3. El código (de
manera esquemática) se puede ver en el listado 7.5.

Código 7.5 Métodos para las torres de Hanoi Hanoi

/∗ ∗
∗ R e a l i z a e l m o v i m i e n t o de una f i c h a de un p o s t e a o t r o .
∗ @param p o s t e 1 E l p o s t e d e s d e e l que s e mueven l a s f i c h a s .
∗ @param p o s t e 2 E l p o s t e a l que s e mueve l a f i c h a .
∗/
p u b l i c v o i d mueveUno ( i n t p o s t e 1 , i n t p o s t e 2 ) {
/∗ E s c r i b e e l número de p o s t e c o r r e s p o n d i e n t e
∗ o d i b u j a e l movimiento ∗/
System . o u t . p r i n t l n ( "Del " + p o s t e 1 + " al " + p o s t e 2 ) ;
}

/∗ ∗
∗ Mueve n f i c h a s d e l poste1 a l poste2 , usando e l
∗ poste3 como p o s t e de t r a b a j o .
∗ @param n e l número de f i c h a s a mover .
∗ @param p o s t e 1 e l p o s t e d e s d e e l c u a l s e mueven .
∗ @param p o s t e 2 e l p o s t e d e s t i n o .
∗ @param p o s t e 3 e l p o s t e de t r a b a j o .
∗/
p u b l i c v o i d mueveN ( i n t n , i n t p o s t e 1 , i n t p o s t e 2 , i n t p o s t e 3 ) {
i f ( n == 2 ) {
mueveUno ( p o s t e 1 , p o s t e 3 ) ;
mueveUno ( p o s t e 1 , p o s t e 2 ) ;
mueveUno ( p o s t e 3 , p o s t e 2 ) ;
}
else {
mueveN ( n  1, p o s t e 1 , p o s t e 3 , p o s t e 2 ) ;
mueveUno ( p o s t e 1 , p o s t e 2 ) ;
mueveN ( n  1, p o s t e 3 , p o s t e 2 , p o s t e 1 ) ;
}
}

Podemos hacer el ejercicio con 4 fichas, llamando al procedimiento con


mueveN(4,1,2,3).Veamos en la figura 7.33 los anidamientos que se hacen. Debe ser
claro que el único método que realmente hace trabajo es mueveUno, ya que para
n ¡ 2 todo lo que se hace es una llamada recursiva. Ilustraremos en las figuras
7.34 a 7.41 cuál es el trabajo realizado en las llamadas a mueveUno.
Comprobemos que este algoritmo trabaja viendo una visualización con cuatro
fichas. En cada figura mostraremos los movimientos que se hicieron mediante
flechas desde el poste en el que estaba la ficha al poste en el que se colocó. Las
reglas exigen que cada vez que se mueve una ficha, ésta sea la que se encuentra
365 Administración de la memoria durante ejecución

hasta arriba.
7.2 Recursividad 366

Figura 7.33 Secuencia de llamadas en la torres de Hanoi

mueveN(4,1,2,3)
¿4  2?
mueveN(3,1,3,2)
¿3  2?
mueveN(2,1,2,3)
¿2  2?
mueveUno(1,3) /* 1 */
mueveUno(1,2) /* 2 */
mueveUno(3,2) /* 3 */
mueveUno(1,3) /* 4 */
mueveN(2,2,3,1)
¿2  2?
mueveUno(2,1) /* 5 */
mueveUno(2,3) /* 6 */
mueveUno(1,3) /* 7 */
mueveUno(1,2) /* 8 */
mueveN(3,3,2,1)
¿3  2?
mueveN(2,3,1,2)
¿2  2?
mueveUno(3,2) /* 9 */
mueveUno(3,1) /* 10 */
mueveUno(2,1) /* 11 */
mueveUno(3,2) /* 12 */
mueveN(2,1,2,3)
¿2  2?
mueveUno(1,3) /* 13 */
mueveUno(1,2) /* 14 */
mueveUno(3,2) /* 15 */
367 Administración de la memoria durante ejecución

Figura 7.34 Situación de las fichas antes de la llamada

Figura 7.35 Movimientos /* 1 */ al /* 3 */

/* 1 */

/* 2 */
/* 3 */

Figura 7.36 Movimiento /* 4 */

/* 4 */
7.2 Recursividad 368

Figura 7.37 Movimientos /* 5 */ al /* 7 */

/* 7 */

/* 6 */
/* 5 */

Figura 7.38 Movimiento /* 8 */

/* 8 */

Figura 7.39 Movimientos /* 9 */ al /* 11 */

/* 10 */

/* 11 */ /* 9 */
369 Administración de la memoria durante ejecución

Figura 7.40 Movimiento /* 12 */

/* 12 */

Figura 7.41 Movimientos /* 13 */ al /* 15 */

/* 13 */

/* 14 */ /* 15 */

Como se puede ver del ejercicio con las torres de Hanoi, 4 fichas provocan
15 movimientos. Podrı́amos comprobar que 5 fichas generan 31 movimientos. Es-
to se debe a la recursividad, que se encuentra “escondida” en la simplicidad del
algoritmo. Aunque definitivamente es más fácil expresarlo ası́, que ocupa aproxi-
madamente 10 lı́neas, que dar las reglas con las que se mueven las fichas de dos
en dos.
Entre otros ejemplos que ya no veremos por el momento, donde la solución
recursiva es elegante y mucho más clara que la iterativa se encuentra el recorrido
de árboles, las búsquedas binarias y algunos ordenamientos como el de mezcla
–Mergesort– y el Quicksort.

Con esto damos por terminado una descripción somera sobre cómo se comporta
la memoria durante la ejecución de un programa, en particular la pila de ejecución.
Esta descripción no pretende ser exhaustiva, sino únicamente proporcionar una
idea de cómo identifica la ejecución los puntos de entrada, de regreso y parámetros
a una función.
En el capı́tulo que sigue veremos la aplicación de recursividad a la solución
del problema de la base de datos que hemos estado manejando, máxime cuando
tenemos una definición de listas dada de manera recursiva.
7. Ejercicios 370

Ejercicios

7.1.- Para la serie de Fibonacci, definida para los números naturales de la siguiente
manera:
#
F ibonaccipnq  1 ¤n¤2 Si 1
F ibonaccipn  2q F ibonaccipn  1q Si n ¡ 2

(a) Escribe el pseudocódigo para esta función recursiva.


(b) ¿Cuántas veces entra la aplicación a la función Fibonacci(int n) cuando se
la invoca con Fibonacci(4) ?
(c) Escribe el código para la función programada iterativamente.
(d) ¿Cuantos pasos se llevan a cabo. contando el incremento del ı́ndice, las
sumas y las copias de un valor a otro?

7.2.- Tenemos el siguiente pedazo de código:


10 package c a p i t 7 ;
20 c l a s s E j e r c i c i o 7 2 {
30 private int caja = 25;
40 public int getCaja () {
50 return caja ;
60 }
70 public void e l E j e r c i c i o ( i n t k ) {
80 k = 50;
90 c a j a ∗= 1 0 ;
100 }
110 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
120 E j e r c i c i o 7 2 nuevo = new E j e r c i c i o 7 2 ( ) ;
130 i n t c a j a = nuevo . g e t C a j a ( ) ;
140 nuevo . e l E j e r c i c i o ( c a j a ) ;
150 System . o u t . p r i n t l n ( "El valor de caja es: " + c a j a ) ;
160 System . o u t . p r i n t l n ( "El segundo valor de caja es: "
170 + nuevo . g e t C a j a ( ) ) ;
180 }
190 }


(a) ¿Qué escribe la aplicación en la lı́nea 150?
(b) ¿Qué escribe la aplicación en las lı́neas 160 u 170?
371 Administración de la memoria durante ejecución

(c) ¿Qué habrı́a que hacer para que el atributo caja, desde main, tomara el
valor 31? (Hay varias formas de lograrlo).

7.3.- Tenemos el registro del grupo en una lista ligada (puedes usar las definiciones
dadas en al capı́tulo 5). Programa, usando Java, recursivamente el método
que lista el grupo completo. La función de listado está definida de la siguiente
manera:
$ #
'
'
' textoGrupo Ð “Grupo: ” grupo
'
'
' Inicio
'
'
'
' $lista Ð lista ! del grupo
'
'
' '
'
'
' '
'
'
lista vacı́a Regresa la cadena vacı́a
& '
Listado '
& ` $
del grupo '
'
'
'
Procesa lista
' '
&Regresa textoGrupo
'
' '
'
'
'
'
' '
' lista vacı́a + primero de la lista
'
' '
% '
% + Procesa lista con
'
'
' + el resto de la lista
'
%Final

7.4.- Para esa misma lista, programa recursivamente la búsqueda de un estudian-


te. La función de búsqueda se encuentra a continuación:
$ "
'
'
' lista vacı́a Reporta
'
'
' no encontrado
'
'
'
& ` $ !
Busca estudiante '
en lista ' '
'
&
Es el primero Repórtalo
'
'
'
'
'
'
'
lista vacı́a
'
` ! Busca estudiante
'
% '
'
%Es el primero en resto de la lista

7.5.- Programa de manera recursiva la inversión de una cadena (Si la cadena es


abcd, su inversión es dcba).

7.6.- Programa recursivamente cómo obtener los distintos patrones de bits con
cuatro posiciones.

7.7.- Programa recursivamente cómo mezclar dos listas de enteros, cada lista or-
denada, de tal manera que el resultado sea una lista ordenada.
7. Ejercicios 372

7.8.- Haz un método que calcule el máximo común divisor (mcd) con la siguiente
fórmula recursiva:
#
mcdpa, bq
b 0
si a
 mcdpb, a % bq si a ¡ 0

7.9.- Diseña, implementa y prueba un método recursivo que eleva un entero po-
sitivo dado a una potencia entera positiva. (Nota: tanto la base como la
potencia deben ser parámetros).

7.10.- Diseña, implementa y prueba un método recursivo que recibe un ente-


ro positivo como parámetro y regresa una cadena representando al ente-
ro con comas en los lugares apropiados. Por ejemplo, si la invocación es
formatea(1000000) deberá regresar “1,000,000”. (Pista: haz la recursión con di-
visión repetida y construye la cadena concatenando después de cada llamada
recursiva)
367 Administración de la memoria durante ejecución

Figura 7.34 Situación de las fichas antes de la llamada

Figura 7.35 Movimientos /* 1 */ al /* 3 */

/* 1 */

/* 2 */
/* 3 */

Figura 7.36 Movimiento /* 4 */

/* 4 */
7.2 Recursividad 368

Figura 7.37 Movimientos /* 5 */ al /* 7 */

/* 7 */

/* 6 */
/* 5 */

Figura 7.38 Movimiento /* 8 */

/* 8 */

Figura 7.39 Movimientos /* 9 */ al /* 11 */

/* 10 */

/* 11 */ /* 9 */
369 Administración de la memoria durante ejecución

Figura 7.40 Movimiento /* 12 */

/* 12 */

Figura 7.41 Movimientos /* 13 */ al /* 15 */

/* 13 */

/* 14 */ /* 15 */

Como se puede ver del ejercicio con las torres de Hanoi, 4 fichas provocan
15 movimientos. Podrı́amos comprobar que 5 fichas generan 31 movimientos. Es-
to se debe a la recursividad, que se encuentra “escondida” en la simplicidad del
algoritmo. Aunque definitivamente es más fácil expresarlo ası́, que ocupa aproxi-
madamente 10 lı́neas, que dar las reglas con las que se mueven las fichas de dos
en dos.
Entre otros ejemplos que ya no veremos por el momento, donde la solución
recursiva es elegante y mucho más clara que la iterativa se encuentra el recorrido
de árboles, las búsquedas binarias y algunos ordenamientos como el de mezcla
–Mergesort– y el Quicksort.

Con esto damos por terminado una descripción somera sobre cómo se comporta
la memoria durante la ejecución de un programa, en particular la pila de ejecución.
Esta descripción no pretende ser exhaustiva, sino únicamente proporcionar una
idea de cómo identifica la ejecución los puntos de entrada, de regreso y parámetros
a una función.
En el capı́tulo que sigue veremos la aplicación de recursividad a la solución
del problema de la base de datos que hemos estado manejando, máxime cuando
tenemos una definición de listas dada de manera recursiva.
7. Ejercicios 370

Ejercicios

7.1.- Para la serie de Fibonacci, definida para los números naturales de la siguiente
manera:
#
F ibonaccipnq  1 ¤n¤2 Si 1
F ibonaccipn  2q F ibonaccipn  1q Si n ¡ 2

(a) Escribe el pseudocódigo para esta función recursiva.


(b) ¿Cuántas veces entra la aplicación a la función Fibonacci(int n) cuando se
la invoca con Fibonacci(4) ?
(c) Escribe el código para la función programada iterativamente.
(d) ¿Cuantos pasos se llevan a cabo. contando el incremento del ı́ndice, las
sumas y las copias de un valor a otro?

7.2.- Tenemos el siguiente pedazo de código:


10 package c a p i t 7 ;
20 c l a s s E j e r c i c i o 7 2 {
30 private int caja = 25;
40 public int getCaja () {
50 return caja ;
60 }
70 public void e l E j e r c i c i o ( i n t k ) {
80 k = 50;
90 c a j a ∗= 1 0 ;
100 }
110 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
120 E j e r c i c i o 7 2 nuevo = new E j e r c i c i o 7 2 ( ) ;
130 i n t c a j a = nuevo . g e t C a j a ( ) ;
140 nuevo . e l E j e r c i c i o ( c a j a ) ;
150 System . o u t . p r i n t l n ( "El valor de caja es: " + c a j a ) ;
160 System . o u t . p r i n t l n ( "El segundo valor de caja es: "
170 + nuevo . g e t C a j a ( ) ) ;
180 }
190 }


(a) ¿Qué escribe la aplicación en la lı́nea 150?
(b) ¿Qué escribe la aplicación en las lı́neas 160 u 170?
371 Administración de la memoria durante ejecución

(c) ¿Qué habrı́a que hacer para que el atributo caja, desde main, tomara el
valor 31? (Hay varias formas de lograrlo).

7.3.- Tenemos el registro del grupo en una lista ligada (puedes usar las definiciones
dadas en al capı́tulo 5). Programa, usando Java, recursivamente el método
que lista el grupo completo. La función de listado está definida de la siguiente
manera:
$ #
'
'
' textoGrupo Ð “Grupo: ” grupo
'
'
' Inicio
'
'
'
' $lista Ð lista ! del grupo
'
'
' '
'
'
' '
'
'
lista vacı́a Regresa la cadena vacı́a
& '
Listado '
& ` $
del grupo '
'
'
'
Procesa lista
' '
&Regresa textoGrupo
'
' '
'
'
'
'
' '
' lista vacı́a + primero de la lista
'
' '
% '
% + Procesa lista con
'
'
' + el resto de la lista
'
%Final

7.4.- Para esa misma lista, programa recursivamente la búsqueda de un estudian-


te. La función de búsqueda se encuentra a continuación:
$ "
'
'
' lista vacı́a Reporta
'
'
' no encontrado
'
'
'
& ` $ !
Busca estudiante '
en lista ' '
'
&
Es el primero Repórtalo
'
'
'
'
'
'
'
lista vacı́a
'
` ! Busca estudiante
'
% '
'
%Es el primero en resto de la lista

7.5.- Programa de manera recursiva la inversión de una cadena (Si la cadena es


abcd, su inversión es dcba).

7.6.- Programa recursivamente cómo obtener los distintos patrones de bits con
cuatro posiciones.

7.7.- Programa recursivamente cómo mezclar dos listas de enteros, cada lista or-
denada, de tal manera que el resultado sea una lista ordenada.
7. Ejercicios 372

7.8.- Haz un método que calcule el máximo común divisor (mcd) con la siguiente
fórmula recursiva:
#
mcdpa, bq
b 0
si a
 mcdpb, a % bq si a ¡ 0

7.9.- Diseña, implementa y prueba un método recursivo que eleva un entero po-
sitivo dado a una potencia entera positiva. (Nota: tanto la base como la
potencia deben ser parámetros).

7.10.- Diseña, implementa y prueba un método recursivo que recibe un ente-


ro positivo como parámetro y regresa una cadena representando al ente-
ro con comas en los lugares apropiados. Por ejemplo, si la invocación es
formatea(1000000) deberá regresar “1,000,000”. (Pista: haz la recursión con di-
visión repetida y construye la cadena concatenando después de cada llamada
recursiva)
Ordenamientos
usando estructuras 8
de datos

8.1 Base de datos en un arreglo

Habiendo ya visto arreglos, se nos ocurre que puede resultar más fácil guardar
nuestras listas de cursos en un arreglo, en lugar de tenerlo en una lista ligada.
Todo lo que tenemos que hacer es pensar en cuál es el tamaño máximo de un
grupo y reservar ese número de localidades en un arreglo. La superclase para
el registro con la información del estudiante queda exactamente igual a la que
utilizamos como EstudianteBasico. Como vamos a acomodar al curso en un arreglo
la relación de quién sigue a quién va a estar dada por la posición en el arreglo. Por
el momento ignoraremos el manejo de listas ligadas y no utilizaremos la referencia
al elemento siguiente en los registros –si la requerimos construiremos una subclase
que la contenga–. Para quien lo requiera puede consultar los listados 6.9 y 6.10 en
el capı́tulo sobre herencia –capı́tulo 6–.
La clase ası́ definida puede ser usada, por ejemplo, para cuando queramos
8.1 Base de datos en un arreglo 374

una lista de estudiantes que tengan esta información básica incluida. Un posible
ejemplo se muestra en el listado 8.1. Esta jerarquı́a se puede extender tanto como
queramos. Si pensamos en estudiantes para listas usamos EstudianteBasico para he-
redar, agregando simplemente campos necesarios para mantener la lista, como en
el caso EstudianteLista del Listado 8.1 –omitiremos los comentarios para JavaDoc
para ahorrar espacio–. Esta clase hereda todos los métodos que implementamos
anteriormente.
Código 8.1 Extendiendo la clase EstudianteBasico EstudianteLista

10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
30
40 p u b l i c c l a s s E s t u d i a n t e L i s t a extends E s t u d i a n t e B a s i c o {
50 protected E s t u d i a n t e L i s t a s i g u i e n t e ;
60
70 public EstudianteLista () {
80 }
90
100 public EstudianteLista ( EstudianteBasico est ) {
110 nombre = e s t . getNombre ( ) ;
120 cuenta = e s t . getCuenta ( ) ;
130 carrera = est . getCarrera ();
140 }
150
160 p u b l i c E s t u d i a n t e L i s t a ( S t r i n g nmbre , S t r i n g c t a , i n t c a r r ) {
170 super ( nmbre , c t a , c a r r ) ;
180 }
190 /∗ ∗
200 ∗ O b t i e n e l a r e f e r e n c i a a l s i g u i e n t e en l a l i s t a .
210 ∗ @ r e t u r n e l v a l o r de s i g u i e n t e
220 ∗/
230 public EstudianteLista getSiguiente () {
240 return t h i s . s i g u i e n t e ;
250 }
260
270 /∗ ∗
280 ∗ A c t u a l i z a e l v a l o r de l a r e f e r e n c i a a l s i g u i e n t e de l a l i s t a .
290 ∗ @param a r g S i g u i e n t e v a l o r p a r a a s i g n a r a t h i s . s i g u i e n t e .
300 ∗/
310 public void s e t S i g u i e n t e ( E s t u d i a n t e L i s t a a r g S i g u i e n t e ) {
320 this . siguiente = argSiguiente ;
330 }
340 }

Si en cambio queremos estudiantes para arreglos, usamos directamente


EstudianteBasico. Por ejemplo, si pensamos en registros para estudiantes de un
curso podemos usar la definición de EstudianteCalifs para armar un curso –ver
Listado 8.2–.
375 Ordenamientos usando estructuras de datos

Código 8.2 Extendiendo la clase EstudianteBasico con calificaciones EstudianteCalifs (1/4)

10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
30 import u t i l e s . ∗ ;

130 p u b l i c c l a s s E s t u d i a n t e C a l i f s extends E s t u d i a n t e B a s i c o {
140 protected f l o a t [ ] c a l i f s ; // Para g u a r d a r c a l i f i c a c i o n e s
150 protected f i n a l int numCalifs ;
160 protected s t a t i c f i n a l i n t NUMCALIFS = 1 0 ;

210 public Es tu d i a n t eC a l i f s () {
220 /∗ En a u t o m á t i c o i n v o c a a l c o n s t r u c t o r s i n p a r á m e t r o s que s e
230 ∗ d e c l a r ó en l a s u p e r c l a s e . ∗/
240 n u m C a l i f s = NUMCALIFS ;
250 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
260 }
270
280 public E s t u d i a n t e C a l i f s ( EstudianteBasico est , int numCalifs ) {
290 super ( e s t ) ;
300 this . numCalifs = numCalifs ;
310 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
320 }

440 p u b l i c E s t u d i a n t e C a l i f s ( S t r i n g nvoNombre , S t r i n g nvaCuenta ,


450 int nvaCarrera , int numCalifs ) {
460 super ( nvoNombre , nvaCuenta , n v a C a r r e r a ) ;
470 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
480 this . numCalifs = numCalifs ;
490 }

570 public float [ ] g e t C a l i f s () {


580 return c a l i f s ;
590 }

660 public void s e t C a l i f s ( f l o a t [ ] califs ) {


670 this . c a l i f s = c a l i f s ;
680 }

760 public void c o p i a C a l i f s ( f l o a t [ ] c a l i f s ) {


770 i f ( c a l i f s == n u l l ) {
780 this . c a l i f s = null ;
790 return ;
800 }
810 t h i s . c a l i f s = new f l o a t [ c a l i f s . l e n g t h ] ;
820 f o r ( i n t i = 0 ; i < c a l i f s . l e n g t h ; i ++) {
830 this . c a l i f s [ i ] = c a l i f s [ i ] ;
840 }
850 }
8.1 Base de datos en un arreglo 376

Código 8.2 Extendiendo la clase EstudianteBasico con calificaciones EstudianteCalifs (2/4)

930 public static float [ ] copia ( float [ ] vector ) {


940 i f ( v e c t o r == n u l l ) {
950 return n u l l ;
960 }
970 f l o a t [ ] v e c t C o p i a = new f l o a t [ v e c t o r . l e n g t h ] ;
980 f o r ( i n t i = 0 ; i < v e c t o r . l e n g t h ; i ++) {
990 vectCopia [ i ] = vector [ i ] ;
1000 }
1010 return vectCopia ;
1020 }
1090 public f l o a t promedio ( ) {
1100 i f ( c a l i f s == n u l l )
1110 return 0;
1120 f l o a t suma = 0 ;
1130 f o r ( i n t i =0; i < c a l i f s . l e n g t h ; i ++) {
1140 suma += c a l i f s [ i ] ;
1150 }
1160 r e t u r n suma / c a l i f s . l e n g t h ;
1170 }
1240 p u b l i c v o i d s e t C a l i f ( f l o a t c u a l , i n t donde ) {
1250 i f ( c a l i f s == n u l l | | donde < 0 | | donde > c a l i f s . l e n g t h )
1260 return ;
1270 c a l i f s [ donde ] = c u a l < 0 ? 0 : Math . min ( c u a l , 1 0 . 0 f ) ;
1280 }
1360 p u b l i c f l o a t g e t C a l i f ( i n t donde ) {
1370 i f ( c a l i f s != n u l l && donde >= 0 && donde < c a l i f s . l e n g t h ) {
1380 r e t u r n c a l i f s [ donde ] ;
1390 }
1400 r e t u r n  1;
1410 }
1500 public String toString () {
1510 S t r i n g l i n e a = super . t o S t r i n g ( ) ;
1520 f o r ( i n t i = 0 ; i < c a l i f s . l e n g t h ; i ++) {
1530 l i n e a = l i n e a + Cadenas . f o r m a t o ( c a l i f s [ i ] , ’ ’ , 7 , 2 ) ;
1540 }
1550 l i n e a = l i n e a + Cadenas . f o r m a t o ( p r o m e d i o ( ) , ’ ’ , 5 , 2 ) ;
1560 return l i n e a ;
1570 }
1580 } // c l a s s

Queremos cambiar nuestra estructura de datos de una lista a un arreglo. Estas


dos estructuras de datos tienen semejanzas y diferencias. Veamos primero las
377 Ordenamientos usando estructuras de datos

semejanzas:
En ambas estructuras existe una noción de orden entre los elementos de la
estructura: podemos determinar cuál elemento va antes y cuál después. Deci-
mos entonces que ambas estructuras son lineales porque podemos “formar”
a los elementos en una lı́nea. En el caso de los arreglos el orden está dado
por el ı́ndice y en el caso de las listas está dado por la posición relativa entre
los elementos.
Todos los elementos de una lista o de un arreglo son del mismo tipo. Decimos
entonces que ambas estructuras son homogéneas.
En cuanto a las diferencias mencionamos las siguientes:
Las listas pueden cambiar de tamaño durante la ejecución, mientras que
los arreglos, una vez definido su tamaño, éste ya no puede cambiar. Las
listas son estructuras dinámicas mientras que los arreglos son estructuras
estáticas.
El acceso al elemento de una lista se lleva a cabo recorriendo cada uno de
los elementos que están antes que el que buscamos; esto es, es un acceso
secuencial ; el acceso a un elemento de un arreglo es mediante un ı́ndice, o
sea acceso directo.
Dependiendo de qué tipo de datos tengamos y cuáles sean las operaciones a
realizar sobre la estructura, podremos elegir entre listas o arreglos para nuestras
estructuras de datos. Esta decisión deberá estar justificada de alguna manera.
Para construir la clase que maneja el arreglo, lo primero que debemos consi-
derar es que en lugar de una cabeza de lista deberemos tener el arreglo, definido
de un cierto tamaño, que corresponderá al máximo número de elementos que es-
peramos. Nuestros algoritmos son exactamente igual, excepto que interpretamos
de distinta manera “toma el siguiente” o “colócate al principio”. En el caso de
que los registros estén en un arreglo “colócate al principio” se interpreta como
“inicializa un ı́ndice en 0”; y “toma el siguiente” se interpreta como “incrementa
en uno a la variable empleada como ı́ndice”. Con esta representación es fácil ob-
tener el anterior, ya que únicamente se decrementa el ı́ndice en 1, si es que existe
anterior. Además, en todo momento tenemos que tener cuidado de no tratar de
tomar elementos más allá del fin del arreglo.
En una primera intención, tomamos directamente a la clase EstudianteCalifs
para colocarlos en la lista guardada en un arreglo. Sin embargo, para saber quién
es el siguiente –ocupa la siguiente posición en el arreglo– o quién es el anterior
–ocupa la posición anterior en el arreglo– vamos a anotar en cada registro la
posición en la que se encuentran. Con este cambio generamos una nueva clase,
EstudianteVector, que hereda de EstudianteCalifs y que tiene un atributo entero.
La implementación de esta clase se encuentra en el listado 8.3 en la siguiente
página –nuevamente omitiremos los comentarios de JavaDoc para hacer eficiente
8.1 Base de datos en un arreglo 378

el uso del espacio–.


Como mencionamos, si el registro se va a encontrar en un arreglo, para obtener
el siguiente (o el anterior) debemos conocer la posición del registro en el arreglo.
Agregamos para ello el atributo pos, que indicará la posición del registro en el
arreglo –se deberá asignar cuando se guarde en el arreglo–. Para marcar que una
posición no está ocupada lo indicamos con un -1 como valor del atributo pos.
También es el valor inicial de la posición mientras el registro no sea ingresado al
arreglo.

Código 8.3 Clase para los elementos del curso (en un arreglo) EstudianteVector

10 package C u r s o V e c t o r ;
20 import u t i l e s . ∗ ;
30 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
40
50 p u b l i c c l a s s E s t u d i a n t e V e c t o r extends E s t u d i a n t e C a l i f s {
60 p r i v a t e i n t p o s ; // Para r e c u p e r a r s u l u g a r en e l a r r e g l o

130 public EstudianteVector () {


140 p o s =  1;
150 }

260 p u b l i c E s t u d i a n t e V e c t o r ( S t r i n g nmbre , S t r i n g cn ta , i n t c r r e r a ,
270 int numCalifs ) {
280 super ( nmbre , c n ta , c r r e r a , n u m C a l i f s ) ;
290 p o s =  1;
300 }

390 public EstudianteVector ( EstudianteBasico est , int numCalifs ) {


400 super ( e s t , n u m C a l i f s ) ;
410 p o s =  1;
420 }

480 public i n t getPos () {


490 return t h i s . pos ;
500 }

130 public void setPos ( i n t argPos ) {


140 t h i s . pos = argPos ;
150 }
160 }

Veamos cómo queda con estos cambios.1 Dado un arreglo es fácil saber cuál
1
Se recomienda referirse a los diagramas de Warnier-Orr donde se dieron los algoritmos en
su momento.
379 Ordenamientos usando estructuras de datos

es el tamaño del arreglo, mediante el atributo length, pero no es igual de fácil


saber el número de posiciones en el arreglo que se encuentran realmente ocupadas
–elementos vivos–. Por lo tanto, es conveniente ir contando los registros que se
agregan y los que se quitan, para que en todo momento se tenga claro el número
de elementos vivos que tenemos en el arreglo. También es necesario mantener a
todos los elementos vivos ocupando posiciones consecutivas a partir de la primera
en el arreglo. En el listado 8.5 en la página 381 podemos ver lo relacionado con el
cambio de estructura de datos de una lista a un arreglo. Se reprograma la interfaz
ServiciosCurso del paquete ConsultasListas para que simplemente regrese objetos de
la clase EstudianteBasico –usando herencia podremos acceder a cualquier objeto de
clases que hereden de EstudianteBasico, como EstudianteVector o EstudianteCalifs–.
El código para esta nueva interfaz se encuentra en el listado 8.4.
Código 8.4 Interfaz para distintas implementaciones del curso ServiciosCurso

10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;

120 p u b l i c i n t e r f a c e S e r v i c i o s C u r s o {
180 S t r i n g getGrupo ( ) ;
250 // S t r i n g daNombre ( i n t c u a l ) ;
260 S t r i n g daNombre ( E s t u d i a n t e B a s i c o c u a l ) ;
330 // S t r i n g d a C a r r e r a ( i n t c u a l ) ;
340 String daCarrera ( EstudianteBasico cual ) ;
410 // S t r i n g daCuenta ( i n t c u a l ) ;
420 S t r i n g daCuenta ( E s t u d i a n t e B a s i c o c u a l ) ;
510 // Es nuevo
520 E s t u d i a n t e B a s i c o l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ,
530 EstudianteBasico desde ) ;
640 E s t u d i a n t e B a s i c o l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ) ;
730 v o i d agregaAlumno ( S t r i n g n , S t r i n g c t a , i n t ca ) ;
790 void eliminaAlumno ( E s t u d i a n t e B a s i c o c u a l ) ;
860 S t r i n g dameLista ( ) ;
920 S t r i n g losQueCazanCon ( S t r i n g q u i e n , i n t campo ) ;
930 } // S e r v i c i o s C u r s o

Con esta interfaz en mente y usando la extensión del método toString para
mostrar el contenido de los objetos, procedemos a implementar una clase para
el curso, guardados los estudiantes en un arreglo. El código se encuentra en el
listado 8.5 en la página 381. Iremos introduciendo los métodos conforme los dis-
cutamos. Por lo pronto presentamos aquellos que no requieren mayor explicación.
8.1 Base de datos en un arreglo 380

Código 8.5 Base de datos implementada en un arreglo CursoEnVector (1/2)

10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;

90 p u b l i c c l a s s C u r s o E n V e c t o r implements S e r v i c i o s C u r s o {
100 protected s t a t i c f i n a l i n t
110 NOMBRE = 1 , // I d d e l campo p a r a nombre
120 CARRERA = 2 , // I d d e l campo p a r a c a r r e r a
130 CUENTA = 3 ; // I d d e l campo p a r a numero de c u e n t a
140 p r i v a t e s t a t i c f i n a l i n t TAM GRUPO = 4 ; // Para l a c l a s e d e l g r u p o
150
160 p r i v a t e i n t NUM CALIFS = 1 0 ; // Núm . de c a l i f i c a c i o n e s p o r alumno
170 p r i v a t e f i n a l i n t MAXREG = 1 0 ; // Núm . de r e g i s t r o s p o r o m i s i ó n
180 p r i v a t e E s t u d i a n t e B a s i c o [ ] l i s t a ; // L i s t a de alumnos
190 private S t r i n g grupo ; // Nombre d e l g r u p o
200 p r i v a t e i n t numRegs = 0 ; // Núm . r e g i s t r o s v i v o s

260 public CursoEnVector ( S t r i n g gr ){


270 grupo = gr ;
280 numRegs = 0 ;
290 l i s t a = new E s t u d i a n t e V e c t o r [MAXREG ] ;
300 }

400 p u b l i c C u r s o E n V e c t o r ( S t r i n g gr , i n t c u a n t o s , i n t n u m C a l i f s ) {
410 NUM CALIFS = n u m C a l i f s ;
420 grupo = gr ;
430 numRegs = 0 ;
440 l i s t a = new E s t u d i a n t e V e c t o r [ c u a n t o s ] ;
450 }

530 public CursoEnVector ( E s t u d i a n t e B a s i c o [ ] i n i c i a l e s , S t r i n g gr ) {


540 grupo = gr ;
550 i f ( i n i c i a l e s . l e n g t h > MAXREG) {
560 l i s t a = new E s t u d i a n t e V e c t o r [ Math . max ( 2 ∗ i n i c i a l e s . l e n g t h ,
570 2 ∗ MAXREG ) ] ;
580 } // end o f i f ( i n i c i a l e s . l e n g t h )
590 else {
600 l i s t a = new E s t u d i a n t e V e c t o r [MAXREG ] ;
610 } // end o f e l s e
620 f o r ( i n t i = 0 ; i < i n i c i a l e s . l e n g t h ; i ++) {
630 l i s t a [ i ] = new E s t u d i a n t e V e c t o r ( i n i c i a l e s [ i ] , NUM CALIFS ) ;
640 (( EstudianteVector ) l i s t a [ i ] ) . setPos ( i ) ;
650 numRegs ++;
660 } // end o f f o r ( i n t i = 0 ; i < i n i c i a l e s . l e n g t h ; i ++)
670 }
381 Ordenamientos usando estructuras de datos

Código 8.5 Base de datos implementada en un arreglo CursoEnVector (2/2)

760 public String toString () {


770 S t r i n g r e s u l t = " GRUPO : " + g r u p o
780 + "\t\tNum. de estudiantes : " + numRegs
790 + "\n ======================================= "
800 + " ===============\ n" ;
810 f o r ( i n t i = 0 ; i < numRegs ; i ++) {
820 i f ( l i s t a [ i ] != n u l l ) {
830 r e s u l t += l i s t a [ i ] . t o S t r i n g ( ) + "\n" ;
840 }
850 } // end o f f o r ( i n t i = 0 ; i < numRegs ; i ++)
860 return r e s u l t ;
870 }
660 p u b l i c S t r i n g daNombre ( E s t u d i a n t e B a s i c o e s t ) {
670 r e t u r n e s t . daNombre ( ) ;
680 }
1050 public String daCarrera ( EstudianteBasico est ) {
1060 return e s t . daCarrera ( ) ;
1070 }
1160 p u b l i c S t r i n g daCuenta ( E s t u d i a n t e B a s i c o e s t ) {
1170 return e s t . getCuenta ( ) ;
1180 }
1280 public String armaRegistro ( EstudianteBasico est ) {
1290 return e s t . t o S t r i n g ( ) ;
1300 }

Hay algunas operaciones básicas que vamos a necesitar al trabajar con arreglos.
Por ejemplo, para agregar a un elemento en medio de los elementos del arreglo
(o al principio) necesitamos recorrer hacia abajo a todos los elementos que se
encuentren a partir de la posición que queremos que ocupe el nuevo elemento.
Esto lo tendremos que hacer si queremos agregar a los elementos y mantenerlos
en orden conforme los vamos agregando.
Similarmente, si queremos eliminar a alguno de los registros del arreglo, tene-
mos que recorrer a los que estén más allá del espacio que se desocupa para que se
ocupe el lugar que se acaba de desocupar. En ambos casos tenemos que recorrer
a los elementos uno por uno y deberemos tener mucho cuidado en el orden en
que recorramos a los elementos. Al recorrer hacia abajo (para hacer lugar) debe-
remos recorrer desde el final del arreglo hacia la primera posición que se desea
mover. Si no se hace en este orden se tendrá como resultado el valor del primer
registro que se desea mover copiado a todos los registros abajo de éste. El método
para recorrer hacia abajo se encuentra en el listado 8.6 en la siguiente página.
El método nos tiene que regresar si pudo o no pudo recorrer a los elementos. En
el caso de que no haya suficiente lugar hacia abajo, nos responderá falso, y nos
8.1 Base de datos en un arreglo 382

responderá verdadero si es que pudo recorrer.


Si deseamos regresar un lugar hacia arriba, el procedimiento es similar, excepto
que tenemos que mover desde el primero hacia el último, para no acabar con una
repetición del último elemento que se recorre. Estos métodos se muestran en el
listado 8.6.
Código 8.6 Corrimiento de registros hacia la derecha e izquierda CursoEnVector

1410 p r i v a t e boolean r e c o r r e ( i n t des de , i n t c u a n t o s ) {


1420 i f ( numRegs + c u a n t o s >= l i s t a . l e n g t h ) {
1430 return f a l s e ;
1440 } // end o f i f ( numRegs + c u a n t o s >= l i s t a . l e n g t h )
1450 f o r ( i n t i = numRegs  1 ; i >= d e s d e ; i ) {
1460 l i s t a [ i + cuantos ] = l i s t a [ i ] ;
1470 l i s t a [ i ] = null ;
1480 (( EstudianteVector ) l i s t a [ i + cuantos ] ) . setPos ( i + cuantos ) ;
1490 } // end o f f o r ( i n t i = numRegs  1 ; i >= d e s d e ; i )
1500
1510 return true ;
1520 }
1640 p r i v a t e boolean r e g r e s a ( i n t des de , i n t c u a n t o s ) {
1650 i f ( ( ( d e s d e  c u a n t o s ) < 0 ) | | ( d e s d e > numRegs  1)) {
1660 return f a l s e ;
1670 } // end o f i f ( ( d e s d e  c u a n t o s ) < 0 )
1680 f o r ( i n t i = d e s d e + c u a n t o s ; i <= numRegs  c u a n t o s ; i ++) {
1690 l i s t a [ i c u a n t o s ] = l i s t a [ i ] ;
1700 ( ( E s t u d i a n t e V e c t o r ) l i s t a [ i  c u a n t o s ] ) . s e t P o s ( i  1);
1710 } // end o f f o r ( i n t i = 0 ; i < numRegs  1 ; i ++)
1720 l i s t a [ numRegs  1] = n u l l ;
1730 return true ;
1740 }

El método que regresaba la referencia de la lista –del primer elemento de la


lista– ahora debe regresar la referencia al arreglo completo. Queda como se muestra
en el listado 8.7, junto con los métodos que ponen y regresan el número de grupo.
Estos dos últimos no cambian.
Código 8.7 Métodos de acceso y manipulación CursoEnVector

1810 public S t r i n g getGrupo ( ) {


1820 return grupo ;
1830 }
1900 public EstudianteBasico [ ] getLista () {
1910 return l i s t a ;
1920 }
1990 p u b l i c i n t getNumRegs ( ) {
2000 r e t u r n numRegs ;
2010 }
383 Ordenamientos usando estructuras de datos

Para saber el número de registros vivos en el arreglo ya no nos sirve “revisarlo”


y ver cuántas referencias distintas de null tiene. Por ejemplo, si el arreglo en vez
de objetos tiene números, y valores válidos pueden ser cero o negativos, no habrı́a
forma de distinguir entre un lugar ocupado por un número o un lugar que no
estuviera ocupado. Por ello es conveniente ir contando los registros que se van
agregando e ir descontando los que se quitan, con los registros activos ocupando
posiciones consecutivas en el arreglo. Por ello, ya no se calcula el número de
elementos en el arreglo, sino que simplemente se regresa este valor.
En implementaciones anteriores usamos tres maneras de agregar registros al
arreglo. La primera de ellas, la más fácil, es agregando al final de los registros, ya
que esto no implica mover a nadie, sino simplemente verificar cuál es el siguiente
lugar a ocupar. De manera similar a que cuando el número de elementos en un
arreglo es n los ı́ndices van del 0 al n  1, si numRegs vale k quiere decir que
los k registros ocupan las posiciones 0 a k  1, por lo que la siguiente posición
a ocupar es precisamente k. En general, numRegs contiene la siguiente posición a
ocuparse, por lo que si se agrega al final, lo único que hay que hacer es ocupar el
lugar marcado por numRegs, incrementando a este último.
Si vamos a agregar los registros siempre al principio del arreglo, lo que tenemos
que hacer es recorrer todos los registros un lugar a la derecha para “desocupar”
el primer lugar y colocar ahı́ el registro.
Por último, si se desea mantener ordenados los registros con un orden lexi-
cográfico, primero tenemos que localizar el lugar que le toca. Una vez hecho esto
se recorren todos los registros a partir de ahı́ un lugar a la derecha, y se coloca en
el lugar desocupado al nuevo registro.
En los tres casos, antes de agregar algún registro deberemos verificar que to-
davı́a hay lugar en el arreglo, ya que el arreglo tiene una capacidad fija dada en el
momento en que se crea. Como no siempre vamos a poder agregar registros (algo
que no sucedı́a cuando tenı́amos una lista), al intentar agregar a un estudiante que
ya no quepa el arreglo deberá aumentar en tamaño con incrementos del tamaño
dado por omisión.
Figura 8.1 Aumento de tamaño de la lista
$ #
'
'
' Tamaño nuevo Ð Tamaño viejo + MAXREG
'
'
' Inicio
'
' nuevaLista Ð arreglo de tamaño nuevo
'
'
Aumenta & #
tamaño
de la lista '
'
' Copia registro nuevaLista[i] Ð lista[i]
'
'
' (i=0,...,numRegs-1)
'
'
' !
'
%Final Regresa nuevaLista
8.1 Base de datos en un arreglo 384

El algoritmo para el método que hace crecer el espacio disponible en la lista se


encuentra en la figura 8.1 en la página anterior y su código en Java en el listado 8.8.

Código 8.8 Aumento del tamaño de la lista CursoEnVector

1470 private EstudianteBasico [ ] copiaLista () {


1480 EstudianteBasico [ ] nuevaLista =
1490 new E s t u d i a n t e B a s i c o [ l i s t a . l e n g t h + MAXREG ] ;
1500 f o r ( i n t i = 0 ; i < l i s t a . l e n g t h ; i ++)
1510 nuevaLista [ i ] = l i s t a [ i ] ;
1520 return nuevaLista ;
1530 }

Con este método es fácil programar los métodos que agregan al principio o al
final de la lista; se muestran en el listado 8.9.

Código 8.9 Inserción de registros al principio y final de la lista CursoEnVector

1610 p u b l i c v o i d a g r e g a E s t F i n a l ( E s t u d i a n t e B a s i c o nuevo ) {
1620 i f ( numRegs >= l i s t a . l e n g t h ) {
1630 EstudianteBasico [ ] nuevaLista ;
1640 nuevaLista = copiaLista ( ) ;
1650 l i s t a = nuevaLista ;
1660 }
1670 l i s t a [ numRegs++] = nuevo ;
1680 }

1770 p u b l i c v o i d a g r e g a E s t ( E s t u d i a n t e B a s i c o nuevo ) {
1780 i f ( numRegs >= l i s t a . l e n g t h ) {
1790 EstudianteBasico [ ] nuevaLista ;
1800 nuevaLista = copiaLista ( ) ;
1810 l i s t a = nuevaLista ;
1820 }
1830 i f (! r e c o r r e (0 , 1)) {
1840 System . o u t . p r i n t l n ( "No se pudo insertar " ) ;
1850 return ;
1860 }
1870 l i s t a [ 0 ] = nuevo ;
1880 }

Para localizar el lugar que le toca a un registro nuevo lo vamos comparando


con los registros en el arreglo, hasta que encontremos el primero “mayor” que él.
Como el orden está dado por el nombre, que es una cadena, tenemos que comparar
385 Ordenamientos usando estructuras de datos

cadenas. Recordemos que el método compareTo de la clase String nos sirve para
saber la relación entre dos cadenas, de la siguiente forma:

$
&1 Si s1   s2
'
'
s1.compareTo(String s2) 0 Si s1 == s2
'
'
% 1 Si s1 ¡ s2

La programación del método que agrega en orden a un estudiante se puede


apreciar en el listado 8.10 en la siguiente página.
8.1 Base de datos en un arreglo 386

Código 8.10 Inserción de registros en lista ordenada CursoEnVector

2590 p u b l i c v o i d agregaAlumno ( E s t u d i a n t e B a s i c o nuevo ) {


2600 i f ( numRegs >= l i s t a . l e n g t h ) {
2610 EstudianteBasico [ ] nuevaLista ;
2620 nuevaLista = copiaLista ( ) ;
2630 l i s t a = nuevaLista ;
2640 }
2650 i n t donde =  1;
2660 S t r i n g nmbre = nuevo . getNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( ) ;
2670 int i = 0;
2680 f o r ( i = 0 ; i < numRegs ; i ++) {
2690 i f ( l i s t a [ i ] == n u l l ) {
2700 System . e r r . p r i n t l n ( "Hay un hueco vacio en la "
2710 + " posicion *" + i + "*" ) ;
2720 continue ;
2730 } // end o f i f ( l i s t a [ i ] == n u l l )
2740 i f ( l i s t a [ i ] . getNombre ( ) . t o L o w e r C a s e ( )
2750 . compareTo ( nmbre . t r i m ( ) . t o L o w e r C a s e ( ) ) >= 0 ) {
2760 donde = i ;
2770 i f (! recorre ( i , 1)) {
2780 return ;
2790 } // end o f i f ( ! r e c o r r e ( i , 1 ) )
2800 break ;
2810 } // end o f i f ( l i s t a [ i ] . getNombre ( ) . comparesTo ( nmbre ) >= 0 )
2820 } // end o f f o r ( i n t i = 0 ; i < numRegs ; i ++)
2830 i f ( i >= numRegs ) { // Le t o c a a l f i n a l
2840 donde = numRegs ;
2850 }
2860 i f ( donde == 1) {
2870 System . o u t . p r i n t l n ( "No pude registrar a " + nmbre ) ;
2880 return ;
2890 } // end o f i f ( donde = 1)
2900 l i s t a [ donde ] = nuevo ;
2910 ( ( E s t u d i a n t e V e c t o r ) l i s t a [ donde ] ) . s e t P o s ( donde ) ;
2920 numRegs++;
2930 }

Para agregar un registro donde nos proporcionan los datos individuales del
estudiante, simplemente armamos el registro e invocamos al método que agrega
un registro ya armado, como se puede ver en el listado 8.11 en la página opuesta.
387 Ordenamientos usando estructuras de datos

Código 8.11 Inserción de registros en lista ordenada con datos individuales CursoEnVector

3000 p u b l i c v o i d agregaAlumno ( S t r i n g nmbre , S t r i n g cn ta , i n t c r r e r a ) {


3010 E s t u d i a n t e B a s i c o nuevo = new E s t u d i a n t e B a s i c o ( nmbre , c nt a ,
3020 crrera );
3030 agregaAlumno ( nuevo ) ;
3040 }

Cuando deseamos agregar un registro de tal manera de mantener la lista en


orden, debemos, como ya dijimos, encontrarle el lugar que le toca, entre un registro
lexicográficamente menor o igual a él y el primero mayor que él. Esto lo hacemos
en las lı́neas 2680 a 2820 del listado 8.10 en la página opuesta. Nos colocamos
al principio del vector, poniendo el ı́ndice que vamos a usar para recorrerlo en 0
–lı́nea 2680–. A continuación, mientras no se nos acaben los registros del arreglo
y estemos viendo registros lexicográficamente menores o iguales al que buscamos
–condicionales en lı́neas 2740 y 2810– incrementamos el ı́ndice, esto es, pasamos
al siguiente.

Podemos salir de la iteración porque se acaben los registros vivos –condición del
for en la lı́nea 2680– o porque se encuentre a un elemento mayor lexicográficamente
–el enunciado break de la lı́nea 2800–. En el primer caso habremos salido porque
el ı́ndice llegó al número de registros almacenados (actual == numRegs), en cuyo
caso simplemente colocamos al nuevo registro en el primer lugar sin ocupar del
arreglo, ya que no hay ningún nombre registrado que sea lexicográficamente mayor.
No hay peligro en esto pues al entrar al método verificamos que todavı́a hubiera
lugares disponibles y si no los habı́a, crecimos el espacio –lı́neas 2600 a 2640–.

En el caso de que haya encontrado un lugar entre dos elementos del arreglo,
tenemos que recorrer a todos los que son mayores que él para hacer lugar. Es-
to se hace en la lı́nea 2770, donde de paso preguntamos si lo pudimos hacer –
seguramente sı́ porque ya habı́amos verificado que hubiera lugar. Una vez reco-
rridos los elementos del arreglo, colocamos el nuevo elemento en el lugar que se
desocupó gracias al corrimiento, y avisamos que todo estuvo bien – lı́neas 2900 y
2920 – no sin antes incrementar el contador de registros.

También el método que quita a un estudiante va a cambiar. Mostramos el


algoritmo en la figura 8.2 en la siguiente página.
8.1 Base de datos en un arreglo 388

Figura 8.2 Algoritmo para eliminar a un estudiante


$ !
'
'
' Inicio donde Ð posición en el arreglo
'
'
' $
'
' '
Eliminar '& '
&
a un Recorrer elementos Recorre un lugar a la izquierda
estudiante '
'
' (i=donde,...,numRegs) '
'
%
'
'
'
'
'
' !
%Final H

La codificación de este algoritmo se puede ver en el listado 8.12.

Código 8.12 Eliminación de un estudiante de la base de datos CursoEnVector

1890 public void eliminaAlumno ( E s t u d i a n t e B a s i c o c u a l ) {


1900 i n t donde = d a L u g a r ( ( E s t u d i a n t e V e c t o r ) c u a l ) ;
1910 System . o u t . p r i n t l n ( " Eliminando a: " + c u a l ) ;
1920 i f ( donde < 0 ) {
1930 return ;
1940 } // end o f i f ( donde < 0 )
1950 r e g r e s a ( donde , 1 ) ;
1960 numRegs ;
1970 l i s t a [ numRegs ] = n u l l ;
1980 }

El método hace lo siguiente: Ubica la posición del registro en el arreglo –lı́nea 1900
del listado 8.12–. Una vez hecho esto –recordemos que agregamos un atributo que
registre esto–, se procede a “desaparecerlo”, recorriendo a los registros que están
después que él un lugar a la izquierda, encimándose en el registro que se está qui-
tando; esto se hace con la llamada a regresa(actual,1) en la lı́nea 1950 del mismo
listado. Al terminar de recorrer a los registros hacia la izquierda, se decrementa
el contador de registros numRegs.
El método que busca una subcadena en alguno de los campos en el arreglo
cambia la forma en que nos colocamos al principio: colocarse al principio ahora
implica poner al ı́ndice que vamos a usar para recorrerlo en 0 – lı́nea 2060 del
listado 8.13 en la página opuesta – mientras que tomar el siguiente quiere decir
incrementar en 1 el ı́ndice que se está usando para recorrer el arreglo – lı́nea 2090
del listado 8.13 en la página opuesta.
389 Ordenamientos usando estructuras de datos

Código 8.13 Búsqueda de una subcadena en algún campo del arreglo (CursoEnVector)
1990 /∗ ∗ Busca a l r e g i s t r o que c o n t e n g a a l a s u b c a d e n a .
2000 ∗ @param i n t c u a l C u a l e s e l campo que s e va a c o m p a r a r .
2010 ∗ @param S t r i n g s u b c a d La c a d e n a que s e e s t á b u s c a n d o .
2020 ∗ @ r e t u r n s i n t E l r e g i s t r o d e s e a d o o  1. ∗/
2030 public E s t u d i a n t e B a s i c o buscaSubcad ( i n t cual , S t r i n g subcad ) {
2040 int actual ;
2050 subcad = subcad . trim ( ) . toLowerCase ( ) ;
2060 actual = 0;
2070 w h i l e ( a c t u a l < numRegs && ( l i s t a [ a c t u a l ] . daCampo ( c u a l ) .
2080 i n d e x O f ( s u b c a d . t o L o w e r C a s e ( ) ) ) == 1)
2090 a c t u a l ++;
2100 i f ( a c t u a l < numRegs )
2110 return l i s t a [ a c t u a l ] ;
2120 else
2130 return n u l l ;
2140 }

Otra diferencia es que cuando en un arreglo preguntamos si ya se nos acabaron


los elementos, lo que hacemos es preguntar si el ı́ndice ya alcanzó al número de
registros – lı́nea 2100 – y no si la referencia es nula. Por lo tanto, recorremos el
arreglo mientras nuestro ı́ndice sea menor que el número de registros – el ı́ndice
sea válido – y no tengamos enfrente – en la posición actual del arreglo – a quien
estamos buscando.
Una vez recorrido el arreglo deberemos averiguar si encontramos o no la sub-
cadena. Si el ı́ndice llegó a ser el número de registros, entonces no lo encontró. Si
no llegó, el entero contenido en el ı́ndice corresponde a la posición de la subcadena
encontrada. El método procede, entonces, a regresar el registro que contiene a la
subcadena.
Una pregunta natural es ¿por qué no regresamos simplemente el ı́ndice en el
que se encuentra el registro? La respuesta es muy sencilla. El ı́ndice tiene sentido
como mecanismo de acceso al arreglo. Sin embargo, el arreglo es un dato privado
de VectorCurso, por lo que desde CursoMenu, y desde cualquier otra clase, no se
tiene acceso a él. Entonces, el conocer una posición en el arreglo, desde fuera de
VectorCurso, no sólo no nos sirve, sino que va contra los principios del encapsula-
miento, en el que los datos son privados en un 99 % de los casos (para hacer un
dato público deberá estar sumamente justificado). Adicionalmente, la clase que
maneja el menú queda prácticamente idéntica a como estaba para el manejo de
las listas, y esto es algo deseable. De esa manera podemos decir que la clase Me-
nuVector no tiene que saber cómo están implementadas las estructuras de datos
o los métodos de la clase VectorLista, sino simplemente saber usarlos y saber que
8.1 Base de datos en un arreglo 390

le tiene que pasar como parámetro y qué espera como resultado. Excepto por
los métodos que agregan y quitan estudiantes, que los volvimos booleanos para
que informen si pudieron o no, todos los demás métodos mantienen la firma que
tenı́an en la implementación con listas ligadas. Vale la pena decir que podrı́amos
modificar los métodos de las listas ligadas a que también contestaran si pudieron
o no, excepto que en el caso de las listas ligadas siempre podrı́an.

Código 8.14 Listar todos los registros de la base de datos (CursoEnVector)


2150 /∗ ∗
2160 ∗ L i s t a todos l o s r e g i s t r o s d e l Curso .
2170 ∗
2180 ∗ @ r e t u r n <code>S t r i n g </code> con l a l i s t a c o m p l e t a
2190 ∗/
2200 public String listaTodos () {
2210 S t r i n g s L i s t a = "" ;
2220 int actual ;
2230 f o r ( a c t u a l = 0 ; a c t u a l < numRegs ; a c t u a l ++)
2240 {
2250 s L i s t a += l i s t a [ a c t u a l ] . t o S t r i n g ( ) ;
2260 }
2270 i f ( a c t u a l == 0 ) {
2280 s L i s t a = "No hay registros en la base de datos " ) ;
2290 }
2300 return s L i s t a ;
2310 }

Nos falta revisar nada más dos métodos: el que lista todo el contenido de la base
de datos y el que lista solamente los que cazan con cierto criterio. Para el primer
método nuevamente se aplica la transformación de que colocarse al principio de la
lista implica poner al ı́ndice que se va a usar para recorrerla en 0 – lı́nea 2240 en el
listado 8.14. Nuevamente nos movemos por los registros incrementando el ı́ndice
en 1, y verificamos al salir de la iteración si encontramos lo que buscábamos o no.
En el caso del método que lista a los que cazan con cierto criterio – listado 8.15
en la página opuesta – nuevamente se recorre el arreglo de la manera que ya vimos,
excepto que cada uno que contiene a la subcadena es listado. Para saber si se listó o
no a alguno, se cuentan los que se van listando – lı́nea 2390 en el listado 8.15 en la
página opuesta. Si no se encontró ningún registro que satisficiera las condiciones
dadas, se da un mensaje de error manifestándolo.
391 Ordenamientos usando estructuras de datos

Código 8.15 Listando los que cumplan con algún criterio (CursoEnVector)
2320 /∗ ∗
2330 ∗ Imprime l o s r e g i s t r o s que c a z a n con un c i e r t o p a t r ó n .
2340 ∗ @param i n t c u a l Con c u á l campo s e d e s e a c o m p a r a r .
2350 ∗ @param S t r i n g s u b c a d Con e l que queremos que c a c e .
2360 ∗ @ r e t u r n <code>S t r i n g </code> l a l i s t a con l o s que c a z a n .
2370 ∗/
2380 p u b l i c S t r i n g losQueCazanCon ( i n t c u a l , S t r i n g s u b c a d ) {
2390 int i = 0;
2400 subcad = subcad . toLowerCase ( ) ;
2410 S t r i n g s L i s t a = "" ;
2420 int actual ;
2430
2440 /∗ ∗ R e c o r r e m o s b u s c a n d o e l r e g i s t r o ∗/
2450 f o r ( a c t u a l = 0 ; a c t u a l < numRegs ; a c t u a l ++) {
2460 i f ( l i s t a [ a c t u a l ] . daCampo ( c u a l ) . i n d e x O f ( s u b c a d ) !=
2470 1) {
2480 i ++;
2490 s L i s t a += l i s t a [ a c t u a l ] . d a R e g i s t r o ( ) ) ;
2500 }
2510 }
2520 /∗ ∗ S i no s e e n c o n t r ó n i n g ú n r e g i s t r o ∗/
2530 i f ( i == 0 ) {
2540 s L i s t a = "No se encontró ningún registro " +
2550 "que cazara " ) ;
2560 }
2570 }

8.2 Mantenimiento del orden con listas ligadas

Tenemos ya una clase que maneja a la base de datos en una lista ligada
(ListaCurso). Podemos modificar levemente ese programa para beneficiarnos de
la herencia y hacer que Estudiante herede de la clase EstudianteBasico, y de esa
manera reutilizar directamente el código que ya tenemos para EstudianteBasico.
Todo lo que tenemos que hacer es agregarle los campos que EstudianteBasico no
tiene y los métodos de acceso y manipulación para esos campos. La programación
de la clase utilizando herencia se puede observar en el listado 8.16 en la siguiente
página.
8.2 Mantenimiento del orden con listas ligadas 392

Código 8.16 Definición de la clase Estudiante para los registros (Estudiante) 1/3
10 import j a v a . u t i l . S c a n n e r ;
20 /∗ ∗
30 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula l a l i s t a
40 ∗ de un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s n o r m a l e s de una
50 ∗ b a s e de d a t o s y f u n c i o n a m e d i a n t e un Menú
60 ∗/
70 c l a s s E s t u d i a n t e extends E s t u d i a n t e B a s i c o {
80 protected E s t u d i a n t e s i g u i e n t e ;
90 protected S t r i n g c l a v e ;
100 p u b l i c s t a t i c f i n a l i n t CLAVE = 4 ;
110 /∗ ∗ C o n s t r u c t o r s i n p a r á m e t r o s . ∗/
120 public Estudiante () {
130 super ( ) ;
140 clave = null ;
150 siguiente = null ;
160 }
170 /∗ ∗
180 ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
190 ∗ Los campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas , m i e n t r a s
200 ∗ que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́ p o r punto
210 ∗ y coma .
220 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
230 ∗ cada uno de l o s campos que s e van a l l e n a r .
240 ∗ @ r e t u r n E s t u d i a n t e una r e f e r e n c i a a una l i s t a
250 ∗/
260 p u b l i c E s t u d i a n t e ( S t r i n g nmbre , S t r i n g cn ta , S t r i n g c l v e ,
270 String crrera ) {
280 super ( nmbre , c n ta , c r r e r a ) ;
290 clave = clve . trim ( ) ;
300 siguiente = null ;
310 }
320 /∗ ∗
330 ∗ R e g r e s a e l c o n t e n i d o d e l campo c l a v e .
340 ∗/
350 public String getClave () {
360 return c l a v e ;
370 }
380 /∗ ∗
390 ∗ A c t u a l i z a e l campo c l a v e con e l v a l o r que p a s a como
400 ∗ p a r á m e t r o .
410 ∗/
420 public void s e t C l a v e ( S t r i n g c l v e ) {
430 clave = clve ;
440 }
393 Ordenamientos usando estructuras de datos

Código 8.16 Definición de la clase Estudiante para los registros (Estudiante)2/3


450 /∗ ∗
460 ∗ R e g r e s a e l campo que c o r r e s p o n d e a l s i g u i e n t e r e g i s t r o
470 ∗ en l a l i s t a
480 ∗/
490 public Estudiante getSiguiente () {
500 return s i g u i e n t e ;
510 }
520 /∗ ∗
530 ∗ R e g r e s a e l campo s e l e c c i o n a d o d e l r e g i s t r o dado .
540 ∗ @param i n t E s t u d i a n t e E l número d e l campo y e l r e g i s t r o .
550 ∗ @ r e t u r n s S t r i n g La c a d e n a s o l i c i t a d a
560 ∗/
570 p u b l i c S t r i n g getCampo ( i n t c u a l ) {
580 S t r i n g cadena ;
590 switch ( c u a l ) {
600 case E s t u d i a n t e B a s i c o .NOMBRE:
610 c a d e n a = getNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( ) ;
620 break ;
630 case E s t u d i a n t e B a s i c o . CUENTA :
640 cadena = getCuenta ( ) . tri m ( ) . toLowerCase ( ) ;
650 break ;
660 case E s t u d i a n t e . CARRERA :
670 cadena = g e t C a r r e r a ( ) . tr i m ( ) . toLowerCase ( ) ;
680 break ;
690 case E s t u d i a n t e . CLAVE :
700 cadena = g e t C l a v e ( ) . tr i m ( ) . toLowerCase ( ) ;
710 break ;
720 default :
730 c a d e n a = " Campo no existente " ;
740 }
750 return cadena ;
760 }
770 /∗ ∗
780 ∗ A c t u a l i z a e l campo s i g u i e n t e con l a r e f e r e n c i a que s e
790 ∗ l a pasa .
800 ∗ @param s i g La r e f e r e n c i a a c o l o c a r .
810 ∗/
820 public void s e t S i g u i e n t e ( E s t u d i a n t e s i g ) {
830 siguiente = sig ; \
840 }
850 /∗ ∗
860 ∗ Arma una c a d e n a con e l c o n t e n i d o de t o d o e l r e g i s t r o .
870 ∗ @ r e t u r n Una c a d e n a con e l r e g i s t r o d e s e a d o .
880 ∗/
890 public String getRegistro () {
900 r e t u r n super . g e t R e g i s t r o ()+"\t"+c l a v e . t r i m ( ) ;
910 }
8.2 Mantenimiento del orden con listas ligadas 394

Código 8.16 Definición de la clase Estudiante para los registros (Estudiante)3/3


920 /∗ ∗
930 ∗ A c t u a l i z a t o d o e l r e g i s t r o de un j a l ó n .
940 ∗ @param S t r i n g e l nombre ,
950 ∗ S tr i n g cuenta
960 ∗ String carrera
970 ∗ String clave .
980 ∗/
990 p u b l i c v o i d s e t R e g i s t r o ( S t r i n g nmbre , S t r i n g cn t a ,
1000 String clve , String crrera ) {
1010 super . s e t R e g i s t r o ( nmbre , c nt a , c r r e r a ) ;
1020 clave = clve . trim ( ) ;
1030 }
1040 }

Hay que notar que lo que programamos como de acceso privado cuando no
tomábamos en consideración la herencia, ahora se convierte en acceso protegido,
para poder extender estas clases.
Algunos de los métodos que enunciamos en esta clase son, simplemente, méto-
dos nuevos para los campos nuevos. Tal es el caso de los que tienen que ver con
clave y siguiente. El método getCampo se redefine en esta clase, ya que ahora tiene
que considerar más posibilidades. También el método getRegistro es una redefini-
ción, aunque usa a la definición de la superclase para que haga lo que correspondı́a
a la superclase.
Los constructores también son interesantes. Cada uno de los constructores,
tanto el que tiene parámetros como el que no, llaman al correspondiente cons-
tructor de la superclase, para que inicialice los campos que tiene en común con la
superclase.
La palabra super se está utilizando de dos maneras distintas. Una de ellas,
en el constructor, estamos llamando al constructor de la superclase usando una
notación con argumentos. En cambio, en el método getRegistro se usa igual que
cualquier otro objeto, con la notación punto. En este segundo caso nos referimos
al “súper-objeto” de this, a aquél definido por la superclase.

8.2.1. Revisita de la clase ListaCurso

Vimos en el capı́tulo anterior prácticamente todos los métodos relativos al


manejo de listas. Nos faltó únicamente el método que agrega registros a la base
de datos, manteniendo el orden. Como en la parte anterior seguiremos teniendo al
395 Ordenamientos usando estructuras de datos

nombre como la llave (key) de nuestros registros, y es el que va a definir el orden.


Igual podrı́amos tener el número de cuenta o la carrera.
Para agregar un registro tenemos que distinguir entre tres situaciones distintas:
(a) La lista está vacı́a, en cuyo caso le toca ser la cabeza de la lista.
(b) Ya hay registros en la lista pero todos van después que el que se está agre-
gando.
(c) Le toca entre dos registros que ya están en la lista, o bien es el último (ambos
casos se tratan igual).
El primer caso es sencillo, ya que simplemente “inauguramos” la lista con el
registro que se nos da. El segundo caso no es igual al tercero porque el nuevo
registro tiene que quedar en la cabeza de la lista – ver figura 8.3, donde las flechas
punteadas son las referencias que deben de quedar. Por lo que la referencia que
queda antes es la de la cabeza de la lista y la que queda después es la que antes
era la primera.

Figura 8.3 Agregando al principio de la lista

lista

Juan Pedro Samuel Yuridia ∅

nuevo Alberto ∅

En el caso de que no sea el primero que se agrega, y que no le toque al principio


de la lista, se debe recorrer la lista hasta que a su izquierda haya uno menor y a
su derecha uno mayor. Esto se logra, simplemente recorriendo la lista y parando
cuando se encuentre el final de la lista, o bien el primero que va a tener una llave
mayor que el nuevo. Una vez que se encontró el lugar, se modifican las referencias
al siguiente para que quede insertado en la lista – ver figura 8.4 en la siguiente
página.
8.2 Mantenimiento del orden con listas ligadas 396

Figura 8.4 Agregando en medio de la lista

lista

Juan Pedro Samuel Yuridia ∅

nuevo Ricardo ∅

El diagrama de Warnier que describe este proceso se encuentra en la figura 8.5


en la página opuesta y la programación del método se puede ver en el listado 8.17.

Código 8.17 Agregar un registro manteniendo el orden (ListaCurso)1/2


1050 /∗ ∗ Agrega un e s t u d i a n t e en o r d e n en l a l i s t a .
1060 ∗ @param E s t u d i a n t e nuevo E l r e g i s t r o a a g r e g a r
1070 ∗ @ r e t u r n s b o o l e a n S i pudo o no h a c e r l o
1080 ∗/
1090 p u b l i c boolean a g r e g a E s t O r d e n ( E s t u d i a n t e nuevo ) {
1100 S t r i n g scompara = nuevo . daNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( ) ;
1110 i f ( l i s t a == n u l l ) // Es e l p r i m e r o que s e mete
1120 l i s t a = nuevo ;
1130 numRegs++;
1140 return true ;
1150 }
1160 i f ( l i s t a . daNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( )
1170 . compareTo ( scompara ) > 0 ) {
1180 // Le t o c a e l p r i m e r l u g a r de l a l i s t a , p e r o
1190 // no e s e l ú n i c o
1200 nuevo . p o n S i g u i e n t e ( l i s t a ) ;
1210 l i s t a = nuevo ;
1220 numRegs++;
1230 return true ;
1240 }
1250 Estudiante actual = l i s t a . daSiguiente () ,
1260 anterior = lista ;
1270 w h i l e ( a c t u a l != n u l l && a c t u a l . daNombre ( ) . t r i m ( )
1280 . t o L o w e r C a s e ( ) . compareTo ( scompara ) <= 0 ) {
1290 anterior = actual ;
1300 actual = actual . daSiguiente ();
1310 }
397 Ordenamientos usando estructuras de datos

Código 8.17 Agregar un registro manteniendo el orden (ListaCurso)2/2


1320 // S i a c t u a l == n u l l l e t o c a a l f i n a l de l a l i s t a
1330 nuevo . p o n S i g u i e n t e ( a c t u a l ) ;
1340 a n t e r i o r . p o n S i g u i e n t e ( nuevo ) ;
1350 numRegs++;
1360 return true ;
1370 }

Figura 8.5 Agregando un registro en orden


$ !
'
'
'
'
lista  H Colócalo en la cabeza de la lista
'
'
' À
'
'
' $ $
'
'
' ' '
'
'
' '
'
'
Le toca al & Insértalo antes del
'
'
' '
'
'
' '
'
'
principio
' primer elemento
'
' ' '
'
'
'
'
'
' de la lista % de la lista
'
' '
'
' À
'
'
' '
'
'
' '
' $
'
' '
'
' '
' anterior Ð Primero de
'
'
' '
' '
'
'
' '
' '
'
'
& '
'
' '
' la lista
Agrega registro '
' '
'
' '
& '
'
' actual Ð Segundo de
en orden '
' '
'
'
'
' lista  H '
'
'
' '
' '
'
' $
la lista
' '
' '
'
'
' '
' Le toca al ' & Recorre ' '
' anterior Ð actual
'
' '
'
' '
'
'
'
' '
' principio la lista ' '
'
' '
' '
' '
'
&
'
' '
' '
'
'
'
' '
'
'
de la lista '
'
(mientras
'
'
' '
' '
' la llave '
'
' '
' '
' '
'
'
'
' '
' '
'
' '
' actual Ð siguiente
'
' '
'
' '
' sea menor '
'
'
'
' '
' '
' '
%
'
' '
' '
'
'
'
' '
'
% '
'
o igual)
% %Insértalo entre anterior y actual

Debemos insistir en que se debe tener cuidado el orden en que se cambian las
referencias. Las referencias que vamos a modificar son la de nuevo y la siguiente
en anterior, que es la misma que tenemos almacenada en actual. Por ello, el orden
para cambiar las referencias podrı́a haber sido
1380 a n t e r i o r . p o n S i g u i e n t e ( nuevo ) ;
1390 nuevo . p o n S i g u i e n t e ( a c t u a l ) ;
8.3 *Ordenamiento usando árboles 398

Lo que debe quedar claro es que una vez modificado anterior.siguiente, esta refe-
rencia ya no se puede usar para colocarla en nuevo.siguiente.

8.3 *Ordenamiento usando árboles

Mantener una lista de cadenas ordenada alfabéticamente es costoso si la lista


la tenemos ordenada en un arreglo o en una lista ligada. El costo se expresa en
términos de operaciones que debemos realizar para llevar a cabo una inserción,
remoción, modificación, etc. de la lista. Por ejemplo, cuando tenemos la lista en
un arreglo, agregar a alguien quiere decir:
Encontrarle lugar
Recorrer a todos los que están después de él un lugar, para hacerle lugar al
nuevo.
Copiar el nuevo a ese lugar.
Similarmente, para quitar a alguien de la lista, debemos recorrer a todos los
que se encuentren después de él para mantener a las cadenas en espacio contiguo.
Para el caso de listas ligadas, la remoción e inserción de registros es un poco
más económica, pero para encontrar a alguien en promedio tenemos que hacer
n{2 comparaciones, donde n es el tamaño de la lista.
Deseamos mantener la lista ordenada, pero queremos bajar los tiempos de
localización de un registro, asumiendo que ésta es la operación más frecuente
que deseamos hacer. Un método bastante eficiente de almacenar información que
se debe mantener ordenada (con respecto a algún criterio) es el de utilizar una
estructura de datos llamada árbol binario.
Empecemos por definir lo que es un árbol, para pasar después a ver, especı́fi-
camente, a los árboles binarios. Un árbol es una estructura de datos dinámica,
no lineal, homogénea. Está compuesta por nodos y los nodos están relacionados
entre sı́ de la siguiente manera:
Cada nodo tiene a un único nodo apuntando a él. A éste se le llama el nodo
padre.
Cada nodo apunta a cero o más nodos. A estos nodos se les llama los hijos.
A los nodos que no tienen hijos se les llama hojas.
Existe un único nodo privilegiado, llamado la raı́z del árbol, que no tiene
padre: corresponde al punto de entrada de la estructura.
A cada nodo le corresponde un nivel en el árbol, y se calcula sumándole 1
al nivel de su padre. La raı́z tiene nivel 1.
399 Ordenamientos usando estructuras de datos

La profundidad del árbol es el máximo de los niveles de sus nodos.


Cada nodo del árbol va a tener un campo para la información que deseamos
organizar, y n referencias, donde n es el número máximo de hijos que puede
tener un nodo.
Un árbol es n-ario si el máximo número de hijos que puede tener es n. Es
binario, si el máximo número de hijos es 2; terciario para tres hijos, y ası́ suce-
sivamente. Podemos representar árboles con un número arbitrario de hijos para
cada nodo, pero dejaremos ese tema para cursos posteriores. También podemos
definir los árboles recursivamente:

$
'
'
&Un nodoÀque no tiene hijos
Un árbol n-ario es
'
'
% Un nodo que tiene como hijos a n árboles

A esta definición le corresponde el esquema en la figura 8.6.

Figura 8.6 Definición recursiva de un árbol

raı́z

Info
......

Árbol Árbol

Árbol

En estos momentos revisaremos únicamente a los árboles binarios, por ser éstos
un mecanismo ideal para organizar a un conjunto de datos de manera ordenada.
Un árbol binario, entonces, es un árbol donde el máximo número de hijos para
cada nodo es dos. Si lo utilizamos para organizar cadenas, podemos pensar que
dado un árbol, cada nodo contiene una cierta cadena. Todas las cadenas que se
encuentran en el subárbol izquierdo son menores a la cadena que se encuentra en
la raı́z. Todas las cadenas que se encuentran en el subárbol derecho, son mayores
8.3 *Ordenamiento usando árboles 400

a la que se encuentra en la raı́z. Un árbol binario bien organizado se muestra en


la figura 8.7.

Figura 8.7 Árbol binario bien organizado

“H”

∅ “A” “P”

“E” ∅ “M” “Y” ∅

“C” ∅ “F” ∅ “N” ∅ ∅ “R”

∅ “B” ∅ ∅ “D” ∅ ∅ “G” ∅ ∅ “T ” ∅

Cuando todos los nodos, excepto por las hojas del último nivel, tienen exac-
tamente el mismo número de hijos, decimos que el árbol está completo. Si la pro-
fundidad del subárbol izquierdo es la misma que la del subárbol derecho, decimos
que el árbol está equilibrado 2 . El árbol del esquema anterior no está ni completo
ni equilibrado.
Para el caso que nos ocupa, la clase correspondientes a cada Estudiante vuelve
a extender a la clase EstudianteBasico, agregando las referencias para el subárbol
izquierdo y derecho, y los métodos de acceso y manipulación de estos campos. La
programación se puede ver en el listado 8.18 en la página opuesta.
Como se ve de la declaración del registro ArbolEstudiante, tenemos una es-
tructura recursiva, donde un registro de estudiante es la información, con dos
referencias a registros de estudiantes.

2
En inglés, balanced
401 Ordenamientos usando estructuras de datos

Código 8.18 Clase ArbolEstudiante para cada registro o nodo (ArbolEstudiante)


10 c l a s s A r b o l E s t u d i a n t e extends E s t u d i a n t e B a s i c o {
20 /∗ R e f e r e n c i a s a s u b á r b o l i z q u i e r d o y d e r e c h o ∗\
30 protected ArbolEstudiante izqrdo ,
40 /∗∗
50 ∗ C o n s t r u c t o r con l a i n f o r m a c i ó n como a r g u m e n t o s .
60 ∗/
70 p u b l i c A r b o l E s t u d i a n t e ( S t r i n g nmbre , S t r i n g cn ta ,
80 String crrera ) {
90 super ( nmbre , c n ta , c r r e r a ) ;
100 izqrdo = dercho = null ;
110 }
120 /∗ ∗
130 ∗ P r o p o r c i o n a l a r e f e r e n c i a d e l s u b á r b o l i z q u i e r d o .
140 ∗/
150 public ArbolEstudiante getIzqrdo () {
160 return izq rdo ;
170 }
180 /∗ ∗
190 ∗ P r o p o r c i o n a l a r e f e r e n c i a a l s u b á r b o l d e r e c h o .
200 ∗/
210 public Arbol Estudi ante getDercho () {
220 return dercho ;
230 }
240 /∗
250 ∗ A c t u a l i z a e l s u b á r b o l i z q u i e r d o .
260 ∗/
270 p u b l i c v o i d s e t I z q r d o ( A r b o l E s t u d i a n t e nuevo ) {
280 i z q r d o = nuevo ;
290 }
300 /∗ ∗
310 ∗ A c t u a l i z a e l s u b á r b o l d e r e c h o .
320 ∗/
330 p u b l i c v o i d s e t D e r c h o ( A r b o l E s t u d i a n t e nuevo ) {
340 d e r c h o = nuevo ;
350 }
360 }

8.3.1. Construcción de árboles binarios para ordenamientos

Los árboles de ordenamiento se van construyendo conforme se van insertando


los nodos. Cuando se inicia la inserción y el árbol está vacı́o, el primer dato que
llega se coloca en la raı́z. A partir de ese momento, si el dato nuevo es menor al
8.3 *Ordenamiento usando árboles 402

de la raı́z, va a quedar en el subárbol izquierdo, mientras que si no es menor va


a quedar en el subárbol derecho. Esto quiere decir que si dos registros tienen la
misma llave (en este caso, nombres iguales pero que el resto de la información
difiere) el segundo que llegue va a quedar “a la derecha” del primero que llegó (en
el subárbol derecho del nodo que contiene al que llegó primero). En el diagrama
del árbol binario que dimos arriba, y si consideramos que la llave es la letra que
aparece, un posible orden de llegada de los registros pudo ser “H”, “P”, “Y”, “R”,
“M”, “A”, “E”, “C”, “F”, “D”, “T”, “B”, “N”. No cualquier orden de llegada
produce el mismo árbol. En este ejemplo, si los registros van llegando ya ordenados
de menor a mayor, el árbol que se obtiene es el que se puede ver en la figura 8.8.

Figura 8.8 Árbol que se forma si los registros vienen ordenados

∅ “A”

∅ “B”

∅ “D”

∅ “E”

∅ “F”

∅ “G”

∅ “H”

∅ “M”

∅ “N”

∅ “P”

∅ “R”

∅ “T”

∅ “Y” ∅

Esto es lo que conocemos como un árbol degenerado, pues degeneró en una


lista. Esta es una situación poco afortunada, pues cuando utilizamos un árbol para
ordenar, queremos que los datos lleguen con un cierto desorden, que nos garantiza
403 Ordenamientos usando estructuras de datos

un árbol más equilibrado. Dados n registros en un árbol, la menor profundidad


se alcanza cuando el árbol está equilibrado, mientras que la mayor profundidad se
alcanza cuando tenemos un árbol degenerado como el que acabamos de mostrar.
Hay varios resultados importantes respecto a árboles binarios que se reflejan en la
eficiencia de nuestras búsquedas. En un árbol equilibrado con n nodos, una hoja
está, en promedio, a distancia log2 n; mientras que la distancia de la cabeza de
una lista a alguno de sus elementos es en promedio de n{2.
Una forma de buscar que el árbol quede equilibrado es mediante el uso de una
raı́z que contenga un dato fantasma, y que este dato fantasma resulte ser un buen
“dato de en medio” para el árbol. Por ejemplo, dado que la “M” se encuentra a
la mitad del alfabeto, un buen dato fantasma para la raı́z pudiera ser un registro
con M’s en él. De esa manera, al menos la mitad de los datos quedarı́a a su
izquierda y la otra mitad a su derecha. En general, asumimos que los datos vienen
desordenados, y en este caso tenemos una distribución adecuada para armar el
árbol.

8.3.2. La clase ArbolOrden

Queremos reutilizar en la medida de lo posible las clases que tenemos tanto


para el manejo de la estructura de datos como del menú que invoca a los métodos
de aquélla. Para ello mantendremos las firmas de los métodos públicos de la clase
ListaCurso, excepto que en todo lugar donde aparece un objeto de la clase Estu-
diante nosotros vamos a usar uno de la clase ArbolEstudiante. Hay dos métodos en
esta clase que no vamos a utilizar – porque la única manera en que vamos a meter
registros es manteniendo el orden – y que son agregaEst y agregaEstFinal. Lo que
vamos a hacer con estos dos métodos es que regresen el valor false ya que no va a
ser cierto que agreguemos ası́.
Otro aspecto importante es que como la estructura de datos es recursiva, lo
ideal es manejarla con métodos recursivos. Pero como la firma de cada uno de los
métodos públicos es fija, lo que haremos en cada uno de los métodos públicos es
desde allı́ llamar por primera vez al método recursivo que va a hacer el trabajo, y
el método recursivo, que va a ser privado, es el que se va a encargar de todo.

8.3.3. Inserción

El algoritmo que nos permite insertar un registro en el árbol es como se muestra


en el diagrama de la figura 8.9 en la página 405. Queda programado, de acuerdo
8.3 *Ordenamiento usando árboles 404

al algoritmo anterior, como se puede apreciar en el listado 8.19 en la página 406.

Del algoritmo en la figura 8.9 en la página opuesta podemos ver que únicamente
podemos agregar un registro cuando le encontramos lugar como una hoja. Lo
que tenemos que hacer es ir bajando por la izquierda o la derecha del árbol,
dependiendo del resultado de comparar al registro que se desea agregar con el que
se encuentra en la raı́z del subárbol en turno. El algoritmo se traduce bastante
directamente a código.
405 Ordenamientos usando estructuras de datos

Figura 8.9 Agregar un registro manteniendo el orden


$
'
'
'
'
' $
'
'
' '
&
'
'
'
'
'
'
Árbol vacı́o Colócalo en la raı́z
'
'
' %
'
'
' À
'
'
'
'
' $ $
'
'
' '
' '
'
'
' '
'
' '
' $
'
'
' '
' '
'
' '
'
' '
' '
' '
'
'
'
' '
'
' '
' & Inserta registro en el
'
'
' '
' '
'
' Hay subárbol
'
' '
'
' '
' '
'
'
' '
' '
'
' izquierdo '
' subárbol izquierdo
'
' '
' Dato menor '
& '
%
'
' '
'
'
'
' '
' À
Inserta ' ' que el de la
'
'
' '
' '
'
'
& '
'
' raı́z '
' $
nodo '
' '
'
' '
'
' '
'
' '
' '
'
en '
' '
' '
' & Coloca el registro como
'
árbol '
' & '
'
'
Hay subárbol
'
'
' '
'
' '
'
'
'
Árbol vacı́o
'
' '
'
izquierdo '
'
%
subárbol izquierdo
'
'
' '
' %
'
' '
'
' À
'
'
' '
'
'
' '
'
' $ #
'
'
' '
' '
'
' '
' '
'
'
Hay subárbol Inserta registro en el
'
' '
'
' '
'
'
'
' '
' '
' derecho subárbol derecho
'
' '
' '
'
& À
'
' '
'
'
Dato menor
'
'
' '
' $
'
' '
'
'
que el de la
'
' '
'
'
'
' '
' '
' &
'
' '
' raı́z '
'
'
'
' '
'
' '
' Hay subárbol
' Coloca el registro como
'
'
' '
% '
% derecho '
%
'
'
'
subárbol derecho
'
%
8.3 *Ordenamiento usando árboles 406

Código 8.19 Agregar un registro en un árbol binario ordenado (ArbolOrden)


10 p u b l i c c l a s s A r b o l O r d e n {
20 /∗ ∗ La r aı́ z d e l á r b o l ∗/
30 private ArbolEstudiante r a i z ;
40 private S t r i n g grupo ;
50 p r i v a t e i n t numRegs ;
60 /∗ ∗ Agrega un r e g i s t r o a l f i n a l de l a l i s t a
70 ∗ @param A r b o l E s t u d i a n t e nuevo E l r e g i s t r o a a g r e g a r .
80 ∗/
90 p u b l i c boolean a g r e g a E s t F i n a l ( A r b o l E s t u d i a n t e nuevo ) {
100 return f a l s e ;
110 }
120 /∗ ∗ Agrega un r e g i s t r o a l p r i n c i p i o de l a l i s t a
130 ∗ @param E s t u d i a n t e B a s i c o nuevo A q u i e n s e va a a g r e g a r
140 ∗/
150 p u b l i c boolean a g r e g a E s t ( A r b o l E s t u d i a n t e nuevo ) {
160 return f a l s e ;
170 }
180 /∗ ∗ Agrega un r e g i s t r o donde l a t o c a p a r a m an t e ne r l a l i s t a
190 ∗ ordenada
200 ∗ @param A r b o l E s t u d i a n t e nuevo A q u i e n s e va a a g r e g a r
210 ∗ @ r e t u r n s b o o l e a n S i pudo o no a g r e g a r
220 ∗/
230 p u b l i c boolean a g r e g a E s t O r d e n ( A r b o l E s t u d i a n t e nuevo ) {
240 i f ( r a i z == n u l l ) { // Es e l p r i m e r o
250 r a i z = nuevo ;
260 return true ;
270 }
280 r e t u r n meteEnArbol ( r a i z , nuevo ) ;
290 }
300 /∗ ∗ Tengo l a s e g u r i d a d de l l e g a r acá con r a i z != n u l l ∗/
310 p r i v a t e boolean meteEnArbol ( A r b o l E s t u d i a n t e r a i z ,
320 A r b o l E s t u d i a n t e nuevo ) {
330 i n t compara = nuevo . daNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( )
340 . compareTo ( r a i z . daNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( ) ) ;
350 i f ( compara < 0 && r a i z . g e t I z q r d o ( ) == n u l l ) {
360 r a i z . p o n I z q r d o ( nuevo ) ;
370 return true ;
380 }
390 i f ( compara >= 0 && r a i z . g e t D e r c h o ( ) == n u l l ) {
400 r a i z . s e t D e r c h o ( nuevo ) ;
410 return true ;
420 }
430 i f ( compara < 0 )
440 r e t u r n meteEnArbol ( r a i z . g e t I z q r d o ( ) , nuevo ) ;
450 else
460 r e t u r n meteEnArbol ( r a i z . g e t D e r c h o ( ) , nuevo ) ;
470 }
407 Ordenamientos usando estructuras de datos

8.3.4. Listar toda la base de datos

Tenemos tres posibles recorridos para listar la información en un árbol bina-


rio ordenado, dependiendo de cuál sea el orden que sigamos para ir listando el
contenido de cada registro:
Recorrido en preorden
Recorrido simétrico
Recorrido en postorden
El siguiente algoritmo que vamos a revisar es el que se encarga de mostrar
o recorrer la lista. Si nuestra lista únicamente tuviera tres registros, y el árbol
estuviera equilibrado, se verı́a como en la figura 8.10 (el orden de llegada pudo
haber sido primero “Anita” y después “Octavio”, pero si hubiera llegado primero
“Anita” u “Octavio” el árbol no se hubiera armado equilibrado).

Figura 8.10 Ejemplo simple para recorridos de árboles

raı́z

“Manuel”

∅ “Anita” ∅ ∅ “Octavio” ∅

Como podemos ver en este esquema, para listar el contenido de los nodos en
orden tenemos que recorrer el árbol de la siguiente manera:

Muestra el hijo izquierdo


Muestra la raı́z
Muestra el hijo derecho
y este orden de recorrido nos entrega la lista en el orden adecuado. A este recorrido
se le conoce como inorden o simétrico, pues la raı́z se visita entre los dos hijos.
Tenemos otros órdenes de recorrido posibles. Por ejemplo, si visitáramos primero
a la raı́z, después al hijo izquierdo y después al hijo derecho, tendrı́amos lo que se
conoce como preorden. Y si primero visitáramos a los dos hijos y por último a la
raı́z, tendrı́amos lo que se conoce como postorden o notación polaca (este nombre
se utiliza porque los árboles son muy útiles no nada más para ordenamientos, sino
8.3 *Ordenamiento usando árboles 408

que se utilizan para denotar la precedencia en operaciones aritméticas). Dado que


los árboles son estructuras recursivas, el hijo izquierdo es, a su vez, un árbol (lo
mismo el hijo derecho). Por ello, si pensamos en el caso más general, en que el
hijo izquierdo pueda ser un árbol tan complicado como sea y el hijo derecho lo
mismo, nuestro algoritmo para recorrer un árbol binario arbitrario quedarı́a como
se muestra en el esquema de la figura 8.11.

Figura 8.11 Recorrido simétrico de un árbol


$ !
'
'
' Hay hijo izquierdo Visita subárbol izquierdo
'
'
' À
'
'
'
'
' $
'
'
' '
&
'
'
'
%H
'
'
'
Hay hijo izquierdo
'
'
'
&
Visita subárbol Procesa la raı́z
'
' !
'
'
' Hay hijo derecho Visita subárbol derecho
'
'
' À
'
'
'
'
' $
'
'
' '
&
'
'
'
'
' %H
Hay hijo derecho
'
% '

En nuestro caso, el proceso del nodo raı́z de ese subárbol en particular consiste
en escribirlo. El método público que muestra toda la lista queda con la firma como
la tenı́a en las otras dos versiones que hicimos, y programamos un método privado
que se encargue ya propiamente del recorrido recursivo, cuya firma será

private void l i s t a A r b o l ( A r b o l E s t u d i a n t e r a i z )

Debemos recordar que éste es un método privado de la clase ArbolOrden, por


lo que siempre que lo invoquemos tendrá implı́cito un objeto de esta clase como
primer argumento. Los métodos listaTodos y listaArbol quedan programados como
se muestra en los listados 8.20 en la página opuesta y 8.21 en la página opuesta
respectivamente.
409 Ordenamientos usando estructuras de datos

Código 8.20 Listado de la base de datos completa (ArbolOrden)


2080 /∗ ∗
2090 ∗ L i s t a t o d o s l o s r e g i s t r o s d e l C u r s o en e l d i s p o s i t i v o de s a l i d a .
2100 ∗/
2110 public void l i s t a T o d o s ( ) {
2120 i f ( r a i z == n u l l ) {
2130 System . o u t . p r i n t l n ( "No hay registros en la base de datos " ) ;
2140 return ;
2150 }
2160 listaArbol ( raiz );
2170 }

Código 8.21 Recorrido simétrico del árbol (ArbolOrden)


2180 /∗ ∗ R e c o r r i d o s i m é t r i c o r e c u r s i v o de un á r b o l .
2190 ∗ @param A r b o l E s t u d i a n t e r a i z La r aı́ z d e l s u b á r b o l .
2200 ∗/
2210 private void l i s t a A r b o l ( A r b o l E s t u d i a n t e r a i z ) {
2220 i f ( r a i z == n u l l )
2230 return ;
2240 listaArbol ( raiz . getIzqrdo ());
2250 System . o u t . i m p r i m e l n ( r a i z . d a R e g i s t r o ( ) ) ;
2260 l i s t a A r b o l ( r a i z . getDercho ( ) ) ;
2270 }

8.3.5. Conservación de la aleatoriedad de la entrada

Quisiéramos guardar el contenido del árbol en disco, para la próxima vez em-
pezar a partir de lo que ya tenemos. Si lo guardamos en orden, cuando lo volvamos
a cargar va a producir un árbol degenerado, pues ya vimos que lo peor que nos
puede pasar cuando estamos cargando un árbol binario es que los datos vengan en
orden (ya sea ascendente o descendente). Por ello, tal vez serı́a más conveniente
recorrer el árbol de alguna otra manera. Se nos ocurre que si lo recorremos en
preorden, vamos a producir una distribución adecuada para cuando volvamos a
cargar el directorio. Esta distribución va a ser equivalente a la original, por lo que
estamos introduciendo un cierto factor aleatorio. El algoritmo es igual de sencillo
que el que recorre en orden simétrico y se muestra en la figura 8.12 en la siguiente
página.
8.3 *Ordenamiento usando árboles 410

Figura 8.12 Recorrido en preorden de un árbol


$
'
'
' Procesa la raı́z
'
' !
'
'
' Hay hijo izquierdo
'
'
' À
Recórrelo en preorden
'
'
'
'
'
' #
'
'
'
'
' Hay hijo izquierdo
Recorrido en
'
& H
preorden '
'
' !
'
'
' Hay hijo derecho
'
'
' À
Recórrelo en preorden
'
'
'
'
' $
'
'
' '
&
'
'
'
%H
'
'
'
Hay hijo derecho
'
%

8.3.6. Búsquedas

Para encontrar un registro dado en el árbol, debemos realizar algo similar a


cuando lo insertamos. Compara con la llave de la raı́z. Si son iguales, ya lo encon-
tramos. Si el que busco es menor, recursivamente busco en el subárbol izquierdo.
Si el que busco es mayor, recursivamente busco en el árbol derecho. Decido que
no está en el árbol, si cuando tengo que bajar por algún hijo, éste no existe. La
única diferencia entre el algoritmo que inserta y el que busca, es la reacción cuan-
do debemos bajar por algún hijo (rama) que no existe. En el caso de la inserción
tenemos que crear al hijo para poder acomodar ahı́ la información. En el caso de
la búsqueda, nos damos por vencidos y respondemos que la información que se
busca no está en el árbol. El algoritmo se muestra en la figura 8.13 en la página
opuesta.

Si no se encuentra al estudiante, el método deberá regresar una referencia nula.


La programación de este método se puede apreciar en el listado 8.22 en la página
opuesta.
411 Ordenamientos usando estructuras de datos

Figura 8.13 Búsqueda en un árbol ordenado


$ !
'
'
' nombre = campo Regresa esta raı́z
'
'
' À
'
'
' $
'
' #
'
'
' '
'
' Hay hijo Encuentra nombre en
'
'
' '
'
'
'
'
' '
'
' izquierdo subárbol izquierdo
'
' '
& À
'
'
'
'
' nombre   campo $
'
' '
' '
'
&
'
'
' '
'
' Hay hijo
'
'
& '
'
' H
' '
Encuentra nombre % izquierdo '
' %
en subárbol '
'
' À
'
'
'
'
' $ #
'
'
' '
'
'
' '
'
'
Hay hijo Encuentra nombre en
'
'
' '
'
'
' '
'
'
derecho
À
subárbol derecho
'
'
' &
'
'
' nombre   campo $
'
'
' '
' '
'
'
' '
' &
' '
' Hay hijo
'
'
' '
'
' ' H
'
% '
% derecho '
%

Código 8.22 Búsqueda del registro con el nombre dado (ArbolOrden)


2280 /∗ ∗ L o c a l i z a e l nodo d e l á r b o l en e l que e s t á e s t e nombre .
2290 ∗ @param A r b o l E s t u d i a n t e r a i z La r aı́ z d e l s u b á r b o l en e l que va
2300 ∗ a buscar .
2310 ∗ @param S t r i n g nombre E l nombre que e s t á b u s c a n d o .
2320 ∗ @ r e t u r n s A r b o l E s t u d i a n t e La r e f e r e n c i a de donde s e e n c u e n t r a
2330 ∗ e s t e r e g i s t r o , o n u l l s i no l o e n c o n t r ó .
2340 ∗/
2350 p r i v a t e A r b o l E s t u d i a n t e buscaDonde ( A r b o l E s t u d i a n t e r a i z ,
2360 S t r i n g nombre ) {
2370 i f ( r a i z == n u l l ) return n u l l ;
2380 i n t compara = nombre . t r i m ( ) . t o L o w e r C a s e ( ) .
2390 compareTo ( r a i z . daNombre ( ) . t r i m ( ) . t o L o w e r C a s e ( ) ) ;
2400 i f ( compara == 0 ) return r a i z ;
2410 i f ( compara < 0 )
2420 r e t u r n buscaDonde ( r a i z . g e t I z q r d o ( ) , nombre ) ;
2430 else
2440 r e t u r n buscaDonde ( r a i z . g e t D e r c h o ( ) , nombre ) ;
2450 }
8.3 *Ordenamiento usando árboles 412

Figura 8.14 Búsqueda de una subcadena

$ !
'
'
' subárbol vacı́o regresa nulo
'
'
' À
'
'
'
'
' !
'
'
' cadena en raı́z regresa la raı́z
'
'
' À
'
'
'
'
' #
'
'
&Hay árbol izquierdo donde Ð Busca cadena en
Busca cadena
en subárbol ' subárbol izquierdo
'
'
'
'
'
' !
'
'
'
' donde  nulo Regresa donde
'
'
' À
'
'
'
'
' #
'
'
' Regresa búsqueda en
'
'
%Hay subárbol derecho subárbol derecho

El otro tipo de búsqueda que tenemos en nuestro programa es el de localizar a


algún registro que contenga a la subcadena pasada como parámetro, en el campo
pasado como parámetro. Para dar satisfacción a esta solicitud debemos recorrer el
árbol hasta encontrar la subcadena en alguno de los registros. El problema es que
como se trata de una subcadena no nos ayuda el orden en el árbol. El algoritmo
para esta búsqueda se muestra en la figura 8.14 mientras que la programación se
muestra en el listado 8.23.

Código 8.23 Búsqueda de subcadena en determinado campo (ArbolOrden)1/2


1770 /∗ ∗
1780 ∗ Busca a l r e g i s t r o que c o n t e n g a a l a s u b c a d e n a .
1790 ∗
1800 ∗ @param i n t c u a l C u a l e s e l campo que s e va a c o m p a r a r .
1810 ∗ @param S t r i n g s u b c a d La c a d e n a que s e e s t á b u s c a n d o .
1820 ∗ @ r e t u r n s i n t E l r e g i s t r o d e s e a d o o  1.
1830 ∗/
1840 public A r b o l E s t u d i a n t e buscaSubcad ( i n t cual , S t r i n g subcad ) {
1850 subcad = subcad . trim ( ) . toLowerCase ( ) ;
1860 r e t u r n p r e O r d e n ( c u a l , subcad , r a i z ) ;
1870 }
413 Ordenamientos usando estructuras de datos

Código 8.23 Búsqueda de subcadena en determinado campo (ArbolOrden) 2/2


1770 /∗ ∗ R e c o r r e e l á r b o l en p r e o r d e n h a s t a e n c o n t r a r una s u b c a d e n a .
1780 ∗ @param i n t c u a l Campo en e l c u a l b u s c a r .
1790 ∗ @param S t r i n g s u b c a d Cadena a l o c a l i z a r .
1800 ∗ @param A r b o l E s t u d i a n t e r a i z R aı́ z d e l á r b o l donde va a b u s c a r .
1810 ∗ @ r e t u r n s A r b o l E s t u d i a n t e E l p r i m e r r e g i s t r o que cumpla
1820 ∗/
1830 p r i v a t e A r b o l E s t u d i a n t e p r e O r d e n ( i n t c u a l , S t r i n g subcad ,
1840 ArbolEstudiante raiz ) {
1850 i f ( r a i z == n u l l ) {
1860 return n u l l ;
1870 }
1880 S t r i n g elCampo = r a i z . daCampo ( c u a l ) . t r i m ( ) . t o L o w e r C a s e ( ) ;
1890 i f ( elCampo . i n d e x O f ( s u b c a d ) != 1) {
1900 return r a i z ;
1910 }
1920 A r b o l E s t u d i a n t e donde = n u l l ;
1930 i f ( r a i z . g e t I z q r d o ( ) != n u l l ) {
1940 donde = p r e O r d e n ( c u a l , subcad , r a i z . g e t I z q r d o ( ) ) ;
1950 }
1960 i f ( donde != n u l l ) {
1970 r e t u r n donde ;
1980 }
1990 i f ( r a i z . g e t D e r c h o ( ) != n u l l ) {
2000 r e t u r n p r e O r d e n ( c u a l , subcad , r a i z . g e t D e r c h o ( ) ) ;
2010 } else {
2020 return n u l l ;
2030 }
2040 }

8.3.7. Listado condicionado de registros

Otro método importante en nuestro sistema es el listado de aquellos registros


que contengan, en el campo elegido, una subcadena. En este caso, al igual que
en el listado de todos los registros, deberemos recorrer todo el árbol en orden
simétrico, de tal manera que al llegar a cada nodo, si es que el nodo contiene a
la subcadena en el lugar adecuado, lo listemos, y si no no. El algoritmo para este
método se parece mucho al que lista a todos los registros, excepto que al visitar la
raı́z, antes de imprimir, verifica si cumple la condición. El algoritmo se muestra en
la figura 8.15 en la siguiente página. La programación de este algoritmo se puede
ver en el listado 8.24 en la siguiente página.
8.3 *Ordenamiento usando árboles 414

Figura 8.15 Selección de registros que cumplen una condición


$ #
'
'
' Visita subárbol
'
'
' Hay hijo izquierdo
'
'
' izquierdo
'
' À
'
'
' $
'
'
' '
'
' &
'
'
'
' %H
Hay hijo izquierdo
'
' '
'
'
' $
'
'
'
'
' '
'
' subcadena en !
'
'
' '
'
' Reporta el registro
'
& '
& Àraı́z
Visita subárbol Procesa la raı́z
'
'
' '
'
' #
'
'
' '
'
' subcadena en
'
' '
%
'
'
'
' # raı́z H
'
'
'
'
'
' Hay hijo derecho
Visita subárbol
'
'
' derecho
'
'
' À
'
'
' $
'
'
' '
'
' &
'
'
'
' %H
Hay hijo derecho
'
% '

Código 8.24 Listado de registros que contienen a una subcadena (ArbolOrden) 1/2
2270 /∗ ∗ Imprime l o s r e g i s t r o s que c a z a n con un c i e r t o p a t r ó n .
2280 ∗ @param i n t c u a l Con c u á l campo s e d e s e a c o m p a r a r .
2290 ∗ @param S t r i n g s u b c a d Con e l que queremos que c a c e .
2300 ∗/
2310 p u b l i c v o i d losQueCazanCon ( i n t c u a l , S t r i n g s u b c a d ) {
2320 subcad = subcad . trim ( ) . toLowerCase ( ) ;
2330 int cuantos = 0;
2340 i f ( r a i z == n u l l )
2350 System . o u t . p r i n t l n ( "No hay registros en la base de"
2360 + " datos " ) ;
2370 else
2380 c u a n t o s = b u s c a Ca z a n ( cons , c u a l , subcad , r a i z ) ;
2390 i f ( c u a n t o s == 0 )
2400 System . o u t . p r i n t l n ( "No hay registros que cacen ." ) ;
2410 }
415 Ordenamientos usando estructuras de datos

Código 8.24 Listado de registros que contienen a subcadena (ArbolOrden) 2/2


2420 /∗ ∗ R e c o r r e e l á r b o l v e r i f i c a n d o s i c a z a n .
2430 ∗ @param c u a l de t i p o <code>i n t </code >. Campo en e l que s e va a b u s c a r .
2440 ∗ @param s u b c a d de t i p o <code>S t r i n g </code >: Cadena a b u s c a r .
2450 ∗ @param r a i z t i p o <code>A r b o l E s t u d i a n t e </code >: R aı́ z d e l á r b o l a r e c o r r e r .
2460 ∗ @ r e t u r n s i n t : E l t o t a l de r e g i s t r o s que c a z a r o n . ∗/
2470 p r i v a t e i n t b u s c a C a z a n ( i n t c u a l , S t r i n g subcad ,
2480 ArbolEstudiante raiz ) {
2490 int este = 0;
2500 i f ( r a i z == n u l l )
2510 return 0;
2520 e s t e = b u s c a Ca z a n ( c u a l , subcad , r a i z . g e t I z q r d o ( ) ) ;
2530 i f ( r a i z . daCampo ( c u a l ) . t r i m ( ) . t o L o w e r C a s e ( ) . i n d e x O f ( s u b c a d )
2540 != 1) {
2550 System . o u t . p r i n t l n ( r a i z . d a R e g i s t r o ( ) ) ;
2560 e s t e ++;
2570 }
2580 e s t e += b u s c a Ca z a n ( c u a l , subcad , r a i z . g e t D e r c h o ( ) ) ;
2590 return e s t e ;
2600 }

8.3.8. Eliminación de nodos

Hemos llegado a la parte más complicada de manejar en un árbol binario,


que es la eliminación de alguno de los nodos del árbol. Si el nodo es una hoja,
realmente no hay ningún problema: simplemente hay que regresar al padre del
nodo y nulificar el apuntador a esa hoja.

Una manera de localizar al padre de un nodo es el volver a realizar la búsqueda


desde la raı́z, pero recordando en todo momento al nodo anterior que se visitó.
Otra manera es la de poner a cada nodo a “apuntar” hacia su padre, lo que se
puede hacer trivialmente en el momento de insertar a un nodo, pues al insertarlo
tenemos un apuntado al padre (de quien lo vamos a colgar) y un apuntador al hijo
(a quien vamos a colgar). Optamos por la primera solución, para no modificar ya
el registro de cada estudiante. La programación del método que localiza al padre
se da en el listado 8.25 en la siguiente página. El algoritmo es similar al que se
dio para la búsqueda de una subcadena, por lo que ya no lo mostramos.
8.3 *Ordenamiento usando árboles 416

Código 8.25 Localización del padre de un nodo (ArbolOrden)


1470 /∗ ∗
1480 ∗ Busca a l p a d r e de un nodo , r e c o r r i e n d o e l á r b o l d e s d e l a
1490 ∗ r aı́ z .
1500 ∗
1510 ∗ @param r a i z t i p o <code>A r b o l E s t u d i a n t e </code >: R aı́ z d e l s u b á r b o l .
1520 ∗ A r b o l E s t u d i a n t e deQuien A q u i e n s e l e b u s c a e l
1530 ∗ padre
1540 ∗ @ r e t u r n s A r b o l E s t u d i a n t e La r e f e r e n c i a d e l p a d r e
1550 ∗/
1560 private ArbolEstudiante buscaPadre ( ArbolEstudiante r a i z ,
1570 A r b o l E s t u d i a n t e deQuien ) {
1580 ArbolEstudiante padre = n u l l ;
1590 i f ( r a i z . g e t I z q r d o ( ) == deQuien )
1600 return r a i z ;
1610 i f ( r a i z . g e t D e r c h o ( ) == deQuien )
1620 return r a i z ;
1630 i f ( r a i z . g e t I z q r d o ( ) != n u l l )
1640 p a d r e = b u s c a P a d r e ( r a i z . g e t I z q r d o ( ) , deQuien ) ;
1650 i f ( p a d r e == n u l l && r a i z . g e t D e r c h o ( ) != n u l l )
1660 r e t u r n b u s c a P a d r e ( r a i z . g e t D e r c h o ( ) , deQuien ) ;
1670 else
1680 return padre ;
1690 }

Como dijimos, una vez que se tiene al padre de una hoja, todo lo que hay que
hacer es identificar si el nodo es hijo izquierdo o derecho y poner el apuntador
correspondiente en null.
Resuelta la eliminación de una hoja, pasemos a ver la parte más complicada,
que es la eliminación de un nodo intermedio. Veamos, por ejemplo, el árbol de
la figura 8.7 en la página 400 y supongamos que deseamos eliminar el nodo eti-
quetado con “E”. ¿Cómo reacomodamos el árbol de tal manera que se conserve
el orden correcto? La respuesta es que tenemos que intercambiar a ese nodo por
el nodo menor de su subárbol derecho. ¿Por qué? Si colocamos al nodo menor
del subárbol derecho en lugar del que deseamos eliminar, se sigue cumpliendo que
todos los que estén en el subárbol izquierdo son menores que él, mientras que
todos los que estén en el subárbol derecho son mayores o iguales que él:
Para localizar el elemento menor del subárbol derecho simplemente bajamos a
la raı́z del subárbol derecho y de ahı́ en adelante seguimos bajando por las ramas
izquierdas hasta que ya no haya ramas izquierdas.El algoritmo para encontrar el
elemento menor de un subárbol se encuentra en la figura 8.16 en la página opuesta.
La programación de este método se muestra en el listado 8.26.
417 Ordenamientos usando estructuras de datos

Figura 8.16 Algoritmo para encontrar el menor de un subárbol


$
'
'
' Baja a hijo derecho
'
& Baja por hijo izquierdo
Encuentra el menor
'
'
' (mientras haya)
'
%
Regresa el último visitado

Código 8.26 Localiza al menor del subárbol derecho (ArbolOrden)


1380 /∗ ∗
1390 ∗ L o c a l i z a a l menor de un s u b á r b o l .
1400 ∗
1410 ∗ @param A r b o l E s t u d i a n t e r a i z La r aı́ z d e l s u b á r b o l
1420 ∗ @ r e t u r n s A r b o l E s t u d i a n t e La r e f e r e n c i a d e l menor
1430 ∗/
1440 p r i v a t e A r b o l E s t u d i a n t e daMenor ( A r b o l E s t u d i a n t e r a i z ) {
1450 A r b o l E s t u d i a n t e menor = r a i z ;
1460 w h i l e ( menor . g e t I z q r d o ( ) != n u l l )
1470 menor = menor . g e t I z q r d o ( ) ;
1480 r e t u r n menor ;
1490 }

Una vez localizado el menor del subárbol derecho, lo intercambiamos con el


nodo que queremos eliminar, en este caso “E”. Con este intercambio, logramos
mover a “E” hacia una hoja, y entonces ya podemos eliminarlo fácilmente. Cabe
aclarar que para intercambiar dos nodos preservando su estructura, basta con
intercambiar el campo correspondiente a la información, dejando las referencias
sin tocar. Pero puede muy fácil suceder que el menor de un subárbol no sea
forzosamente una hoja. Entonces, lo que debemos hacer con él es, nuevamente
intercambiarlo con el menor de su subárbol derecho.
Pudiera presentarse el caso de que el nodo no tuviera subárbol izquierdo. En
ese caso, debemos “subir” el subárbol derecho al lugar que ocupa ahora el nodo
que deseamos eliminar. Por ejemplo, si en el árbol anterior original deseáramos
eliminar al nodo que contiene una “A” (que no tiene subárbol izquierdo), sim-
plemente “subimos” al subárbol etiquetado con “D” y todas las relaciones entre
los nodos se mantienen. En realidad, si el nodo tiene sólo a uno de los hijos (sea
el izquierdo o el derecho) el nodo se elimina fácilmente subiendo al subárbol que
sı́ tiene a su lugar.
8.3 *Ordenamiento usando árboles 418

Otra situación a considerar es cuando se desea eliminar a la raı́z del árbol. En


ese caso no vamos a tener padre, aún cuando el único nodo en el árbol sea éste. A
la raı́z se le debe tratar de manera un poco diferente. El algoritmo para eliminar
a un nodo se muestra en la figura 8.17 en la página opuesta. La programación del
método se puede ver en el listado 8.27 en la página 420.
419 Ordenamientos usando estructuras de datos

Figura 8.17 Eliminación de un nodo en un árbol

$ $ !
'
' '
'
' El nodo es hoja
'
'
' '
' À
Elimina a la raı́z
'
' '
'
'
'
'
' '
' #
'
' '
'
'
'
'
' '
'
El nodo tiene sólo Pon al subárbol derecho
'
' '
'
'
'
'
' '
'
subárbol derecho en la raı́z
'
' '
' À
'
' '
& #
'
'
'
'
'
'
nodo == raı́z El nodo tiene sólo
' Pon al subárbol izquierdo
'
' '
'
'
'
'
' '
' subárbol izquierdo en la raı́z
'
' '
'
' À
'
'
' '
'
'
' '
' $
'
' '
'
' '
'
'
'
' '
' &Localiza menor en subárbol derecho
'
' '
'
'
El nodo tiene
'
'
' '
' ' Intercambia a menor y nodo
'
' % ambos hijos '
%
'
& Elimina a quien quedó en menor
Elimina À
nodo ' ' $ !
'
'
' '
'
' '
'
'
El nodo es hoja Anula el apuntador del padre
'
' '
' À
'
'
' '
'
'
' '
'
' #
'
'
' '
'
'
' '
'
'
El nodo tiene sólo Sube al subárbol derecho
'
'
' '
'
'
' '
'
'
subárbol derecho
À
al lugar del nodo
'
'
' '
'
'
' & #
'
'
' nodo == raı́z El nodo tiene sólo Sube al subárbol izquierdo
'
'
' '
'
'
' '
'
'
'
'
' '
'
subárbol izquierdo
À
al lugar del nodo
'
' '
'
'
'
'
' '
' $
'
' '
'
' '
'
' '
' '
&Localiza menor en subárbol derecho
'
'
' '
' El nodo tiene
'
' '
'
' Intercambia a menor y nodo
'
% '
% ambos hijos '
'
% Elimina a quien quedó en menor
8.3 *Ordenamiento usando árboles 420

Código 8.27 Eliminación de un nodo en el árbol (ArbolOrden) 1/3


570 /∗ ∗
580 ∗ D e f i n e s i un nodo d e l á r b o l e s h o j a ∗/
590 p r i v a t e boolean e s H o j a ( A r b o l E s t u d i a n t e nodo ) {
600 i f ( nodo . g e t I z q r d o ( ) == n u l l && nodo . g e t D e r c h o ( ) == n u l l )
610 return true ;
620 return f a l s e ;
630 }
640 /∗ ∗
650 ∗ Q u i t a e l r e g i s t r o s o l i c i t a d o ( p o r nombre ) de l a l i s t a
660 ∗ @param S t r i n g nombre E l nombre d e l r e g i s t r o ∗/
670 p u b l i c boolean q u i t a E s t ( S t r i n g nombre ) {
680 A r b o l E s t u d i a n t e donde = buscaDonde ( r a i z , nombre ) ;
690 i f ( donde == n u l l )
700 return f a l s e ;
710 i f ( donde == r a i z && e s H o j a ( r a i z ) ) {
720 raiz = null ;
730 return true ;
740 }
750 i f ( donde == r a i z . g e t I z q r d o ( )
760 && e s H o j a ( r a i z . g e t I z q r d o ( ) ) ) {
770 r a i z . ponIzqrdo ( null ) ;
780 return true ;
790 }
800 i f ( donde == r a i z . g e t D e r c h o ( )
810 && e s H o j a ( r a i z . g e t D e r c h o ( ) ) ) {
820 r a i z . ponIzqrdo ( null ) ;
830 return true ;
840 }
850 r e t u r n b o r r a A ( donde ) ;
860 }
870 p r i v a t e boolean b o r r a A ( A r b o l E s t u d i a n t e donde ) {
880 A r b o l E s t u d i a n t e conQuien ;
890 i f ( donde == r a i z . g e t I z q r d o ( )
900 && e s H o j a ( r a i z . g e t I z q r d o ( ) ) ) {
910 r a i z . ponIzqrdo ( null ) ;
920 return true ;
930 }
940 i f ( donde == r a i z . g e t D e r c h o ( )
950 && e s H o j a ( r a i z . g e t D e r c h o ( ) ) ) {
960 r a i z . ponIzqrdo ( null ) ;
970 return true ;
980 }
990 ArbolEstudiante padre ;
1000 i f ( donde == r a i z )
1010 padre = n u l l ;
1020 else
1030 p a d r e = b u s c a P a d r e ( r a i z , donde ) ;
421 Ordenamientos usando estructuras de datos

Código 8.27 Eliminación de un nodo en el árbol (ArbolOrden)2/3


1040 i f ( donde . g e t I z q r d o ( ) == n u l l && donde . g e t D e r c h o ( ) != n u l l
1050 && p a d r e == n u l l ) {
1060 // Es l a r aı́ z y t i e n e nada más s u b á r b o l d e r e c h o }
1070 r a i z = donde . g e t D e r c h o ( ) ;
1080 return true ;
1090 }
1100 i f ( donde . g e t I z q r d o ( ) != n u l l && donde . g e t D e r c h o ( ) == n u l l
1110 && p a d r e == n u l l ) {
1120 // S ó l o hay s u b á r b o l i z q u i e r d o
1130 r a i z = donde . g e t I z q r d o ( ) ;
1140 return true ;
1150 }
1160
1170 i f ( p a d r e == n u l l ) {
1180 // T i e n e a l o s d o s s u b á r b o l e s
1190 conQuien = daMenor ( donde . g e t D e r c h o ( ) ) ;
1200 donde . ponNombre ( conQuien . daNombre ( ) ) ;
1210 donde . ponCuenta ( conQuien . daCuenta ( ) ) ;
1220 donde . p o n C a r r e r a ( conQuien . d a C a r r e r a ( ) ) ;
1230 r e t u r n b o r r a A ( conQuien ) ;
1240 }
1250
1260 i f ( donde . g e t I z q r d o ()== n u l l
1270 && donde . g e t D e r c h o ( ) == n u l l ) {
1280 // Es h o j a
1290 i f ( p a d r e . g e t I z q r d o ()==donde ) {
1300 padre . ponIzqrdo ( n u l l ) ;
1310 return true ;
1320 }
1330 i f ( p a d r e . g e t D e r c h o ( ) == donde ) {
1340 padre . setDercho ( n u l l ) ;
1350 return true ;
1360 }
1370 }
1380 i f ( donde . g e t I z q r d o ( ) == n u l l ) {
1390 // S ó l o t i e n e s u b á r b o l d e r e c h o
1400 i f ( p a d r e . g e t I z q r d o ( ) == donde ) {
1410 p a d r e . p o n I z q r d o ( donde . g e t D e r c h o ( ) ) ;
1420 return true ;
1430 }
1440 i f ( p a d r e . g e t D e r c h o ( ) == donde ) {
1450 p a d r e . s e t D e r c h o ( donde . g e t D e r c h o ( ) ) ;
1460 return true ;
1470 }
1480 }
8.3 *Ordenamiento usando árboles 422

Código 8.27 Eliminación de un nodo en el árbol (ArbolOrden) 3/3


1490 i f ( donde . g e t D e r c h o ( ) == n u l l && p a d r e != n u l l ) {
1500 i f ( p a d r e . g e t I z q r d o ( ) == donde ) {
1510 p a d r e . p o n I z q r d o ( donde . g e t I z q r d o ( ) ) ;
1520 return true ;
1530 }
1540 i f ( p a d r e . g e t D e r c h o ( ) == donde ) {
1550 p a d r e . s e t D e r c h o ( donde . g e t I z q r d o ( ) ) ;
1560 return true ;
1570 }
1580 }
1590 // Ambos s u b á r b o l e s e x i s t e n
1600 conQuien = daMenor ( donde . g e t D e r c h o ( ) ) ;
1610 donde . ponNombre ( conQuien . daNombre ( ) ) ;
1620 donde . ponCuenta ( conQuien . daCuenta ( ) ) ;
1630 donde . p o n C a r r e r a ( conQuien . d a C a r r e r a ( ) ) ;
1640 r e t u r n b o r r a A ( conQuien ) ;
1650 } // b o r r a A

Por último, el mecanismo para modificar algún registro no puede ser, sim-
plemente, modificar la información, pues pudiera ser que la llave cambiara y el
registro quedara fuera de orden. La estrategia que vamos a utilizar para modificar
la información de un registro es primero borrarlo y luego reinsertarlo, para evitar
que se modifique la llave y se desacomode el árbol.

8.3.9. La clase MenuOrden


Utilizamos lo que tenemos programado en MenuLista para copiarlo a MenuOr-
den. Sustituimos la llamada a agregaEst por una llamada a agregaEstOrden para
que los vaya acomodando bien en el árbol. Asimismo, todos los parámetros o va-
lores de retorno que antes eran de la clase Estudiante ahora pasan a ser de la clase
ArbolEstudiante.
Adicionalmente, en el método main cuando se declaran y crean los objetos
miCurso y miMenu, se cambian las clases a que ahora sean de las clases ArbolOrden y
MenuArbol respectivamente. Como tuvimos mucho cuidado en mantener la interfaz
tal cual, con todos los métodos conservando su firma, no hay necesidad de realizar
ningún otro cambio.
Con esto damos por terminado cómo mantener ordenada una lista de acuerdo
a cierta llave, que corresponde a uno de los campos. Hay muchos algoritmos para
ordenar una lista, pero no es material de estos temas, por lo que no lo revisaremos
acá.
423 Ordenamientos usando estructuras de datos

Ejercicios

8.1.- Supongamos que tenemos un arreglo de enteros ordenados, ocupando posi-


ciones consecutivas en el arreglo. Podemos hacer una búsqueda muy eficiente
en el arreglo usando una búsqueda binaria, que funciona de la siguiente ma-
nera:
a) Se considera al elemento en la posición correspondiente a la mitad del
arreglo.
1.1. Si es el que buscamos, ya terminamos.
1.2. Si este elemento es mayor que el que buscamos, repetimos la búsque-
da en la mitad izquierda del arreglo.
1.3. Si este elemento es menor que el que buscamos, repetimos la
búsqueda en la mitad derecha del arreglo.
b) Terminamos cuando el arreglo restante por buscar sólo tenga una po-
sición y no contenga el entero que buscamos.
Diseña, programa y prueba la búsqueda binaria en un arreglo.

8.2.- Tienes una lista de enteros que está ordenada y almacenada en lugares con-
tiguos de un arreglo. Diseña, programa y prueba un método para insertar a
un nuevo entero en el lugar que le corresponde –dado un entero duplicado, el
último que ingresa va después del primero que ingresa, pero sı́ debe aparecer
tantas veces como se presente–.

8.3.- Para el ejercicio anterior, inserta código en tu método que cuente el número
de movimientos que se tienen que hacer cada vez que se ingresa un elemento.

8.4.- Como en el ejercicio anterior, pero el método debe eliminar al entero en la


posición i, manteniendo los registros ocupando posiciones consecutivas. Haz
el código necesario para que se lleve la cuenta del número de movimientos
necesarios para eliminar a un elemento.

8.5.- Diseña, programa y prueba un método que mezcle los enteros de dos arreglos
que vienen en orden cada uno, para obtener un único arreglo que incluya a
ambas listas en orden.

8.6.- Diseña, programa y prueba un método que incorpore a una lista de enteros
que se encuentran en un arreglo a otra lista de enteros que se encuentran en
otro arreglo. Se debe considerar el agrandar al arreglo anfitrión. Incorpora
al método el número de movimientos y copias que debes hacer.
8. Ejercicios 424

8.7.- Lo mismo que el ejercicio anterior, excepto que los enteros se encuentran en
una lista ligada. Incorpora al método el número de movimientos y copias
que debes hacer.

8.8.- Cuando decimos que los arreglos y las listas ligadas corresponden a la misma
estructura de datos, ¿a qué se refiere esta afirmación?

8.9.- ¿Cómo se representarı́an con listas los arreglos de dos dimensiones?

8.10.- Si tenemos una lista de enteros cuyas magnitudes son relativamente pe-
queñas (por ejemplo, números entre 100 y 100, podrı́amos usar arreglos
sin exigir que todos los elementos estén en posiciones consecutivas. Por ejem-
plo, a cada entero le podemos asignar la posición i 100 y acomodarı́amos
a todos los enteros. Dado este esquema:
(a) ¿Cómo manejas los números repetidos?
(b) ¿Cómo sabes cuántos enteros están realmente en la lista?
(c) ¿Qué haces si deseas alguna información adicional al entero?
Nota: A este tipo de almacenamiento se le conoce como función de disper-
sión, donde a partir de los datos se calcula una posición en la que se
va a acomodar el registro.
Manejo de errores
en ejecución 9
9.1 Tipos de errores

Todos hemos padecido en algún momento errores de ejecución. Java tiene me-
canismos muy poderosos para detectar errores de ejecución de todo tipo, a los que
llama excepciones. Por ejemplo, si se trata de usar una referencia nula, Java ter-
minará (abortará) el programa con un mensaje de error. Generalmente el mensaje
tiene la forma:
Exception in thread "main" java.lang.NullPointerException
at MenuLista.main(MenuLista.java:151)
En el primer renglón del mensaje Java nos dice el tipo de excepción que causó que
el programa “abortara”, que en este caso es NullPointerException, y a partir del
segundo renglón aparece la “historia” de la ejecución del programa, esto es, los
registros de activación montados en la pila de ejecución en el momento en que
sucede el error. Hay muchos errores a los que Java va a reaccionar de esta manera,
como por ejemplo usar como ı́ndice a un arreglo un entero menor que cero o ma-
yor o igual al tamaño declarado (ArrayIndexOutOfBoundsException) o una división
entre 0 (ArithmeticException).
9.1 Tipos de errores 426

La manera como responde la máquina virtual de Java cuando sucede un error


de ejecución es que lanza lo que se conoce como una excepción. Una excepción
es un objeto de alguna clase que hereda de la clase Throwable y que contiene infor-
mación respecto a dónde se produjo el error y qué tipo de error es. Dependiendo
de la clase a la que pertenece el objeto, fue el tipo de error que hubo. También
el programador puede crear sus propias excepciones, como un mecanismo más de
control del flujo del programa.
Dos clases importantes extienden a la clase Throwable: Error y Exception. El
programador puede crear sus propias excepciones extendiendo a la clase Excep-
tion1 .

9.1.1. Excepciones en tiempo de ejecución (RuntimeException)

Una excepción incluye dos conceptos importantes: lanzar la excepción, que


sucede en el punto donde algo anómalo se presenta, y manejar la excepción, que
define cómo deberá reaccionar la Máquina Virtual de Java2 cuando se presenta la
excepción.
Entre las excepciones que pueden causarse (o lanzarse) en la ejecución de una
aplicación hay de dos tipos: aquellas que tiene que ser “vigiladas” (la aplicación
tiene que incluir código por si el error sucede) y aquellas que, como son muy
comunes o implican un error en la lógica del programa que hay que corregir, no
hay que vigilarlas –aunque se puede hacerlo–. Bajo “comunes” queremos decir
que se pueden presentar casi en cualquier programa, por simple que éste sea, y en
muchos tipos de enunciados, por lo que si estuviésemos obligados a vigilarlas el
código crecerı́a mucho y se harı́a muy poco claro.
Las excepciones que no deben ser vigiladas, que revisaremos primero, son co-
nocidas como “de tiempo de ejecución” (RuntimeException)3 . Para ejemplificarlas
juntaremos varios métodos –en los que las excepciones pueden ser lanzadas, el
error en ejecución puede ocurrir– en una clase ExcepcionesRuntimeSimples e ire-
mos presentando cada una de las que nos interesan. Implementaremos para cada
una un método y elegiremos el método a ejecutar pasando un argumento a main;
la implementación de esta clase se encuentra en el listado 9.1. Iremos presentando
cada uno de los métodos invocados conforme lo discutamos.
1
Es conveniente que aquellas clases que heredan a la clase Exception su nombre termine con
“Exception” y el resto del nombre sirva para describir el tipo de error que está detectando.
2
En adelante JVM.
3
El nombre no es muy adecuado ya que todas las excepciones se presentan en tiempo de
ejecución.
427 Manejo de errores en ejecución

Código 9.1 Clase para causar excepciones ExcepcionesRuntimeSimples (1/3)

10 package e x c e p c i o n e s ;
20 import j a v a . u t i l . S c a n n e r ;
30 import j a v a . u t i l . I n p u t M i s m a t c h E x c e p t i o n ;
40
50 public class ExcepcionesRuntimeSimples {
60 /∗ ∗ E j e m p l i f i c a A r i t h m e t i c E x c e p t i o n ∗/
70 p r i v a t e s t a t i c double c a l c u l a ( i n t k ) {
80 i n t n = 100 / k ;
90 double x = 1000 / k ;
100 r e t u r n Math . max ( n , x ) ;
110 }
120 /∗ ∗ E j e m p l i f i c a A r r a y S t o r e E x c e p t i o n ∗/
130 private s t a t i c void guardaObjeto ( Object [ ] A, i n t i , Object o b j e t o ){
140 A[ i ] = objeto ;
150 }
160 /∗ ∗ E j e m p l i f i c a C l a s s C a s t E x c e p t i o n ∗/
170 private s t a t i c void i n t e r p r e t a O b j e t o ( I n t e g e r [ ] A, i n t i ,
180 Object objeto ) {
190 A[ i ] = ( I n t e g e r ) objeto ;
200 }
210 /∗ ∗ E j e m p l i f i c a I l l e g a l A r g u m e n t E x c e p t i o n ∗/
220 p r i v a t e s t a t i c double r a i z C u a r t a ( double x ) {
230 r e t u r n Math . s q r t ( Math . s q r t ( x ) ) ;
240 }
250 /∗ ∗ E j e m p l i f i c a I n d e x O u t O f B o u n d s E x c e p t i o n ∗/
260 p r i v a t e s t a t i c i n t maximo ( i n t [ ] v a l o r e s , i n t maxInd ) {
270 i n t maxi = v a l o r e s [ 0 ] ;
280 f o r ( i n t i =0; i <= maxInd ; i ++) {
290 try {
300 i f ( maxi < v a l o r e s [ i ] ) {
310 maxi = v a l o r e s [ i ] ;
320 }
330 } catch ( A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n e ) {
340 throw new A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n
350 ( " tamanho del arreglo :"
360 + v a l o r e s . l e n g t h +"\n"
370 + " valor de i="+i ) ;
380 }
390 }
400 r e t u r n maxi ;
410 }
420 /∗ ∗ E j e m p l i f i c a N e g a t i v e A r r a y S i z e E x c e p t i o n ∗/
430 p r i v a t e s t a t i c i n t [ ] l l e n a A r r e g l o ( i n t tamanho , i n t v a l o r ) {
440 i n t [ ] a r r e g l o = new i n t [ tamanho ] ;
450 f o r ( i n t i = 0 ; i < tamanho ; i ++) {
460 arreglo [ i ] = valor ;
470 }
480 return a r r e g l o ;
490 }
9.1 Tipos de errores 428

Código 9.1 Clase para causar excepciones ExcepcionesRuntimeSimples (2/3)

500 /∗ ∗ E j e m p l i f i c a N u l l P o i n t e r E x c e p t i o n ∗/
510 private s t a t i c void l l e n a A r r e g l o ( I n t e g e r [ ] a r r e g l o , i n t v a l o r ){
520 f o r ( i n t i = 0 ; i < a r r e g l o . l e n g t h ; i ++) {
530 a r r e g l o [ i ] = new I n t e g e r ( v a l o r ) ;
540 }
550 }
560 /∗ ∗ E j e m p l i f i c a I n p u t M i s m a t c h E x c e p t i o n de j a v a . u t i l ∗/
570 private s t a t i c void leeNumeros ( ) {
580 S c a n n e r s I n = new S c a n n e r ( System . i n ) ;
590 while ( true ) {
600 System . o u t . p r i n t l n ( "Dame un número" ) ;
610 i n t num = s I n . n e x t I n t ( ) ;
620 System . o u t . p r i n t l n ( "El número leı́do es:" + num ) ;
630 }
640 System . o u t . p r i n t l n ( "Salı́ del while " ) ;
650 }
660
670 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
680 S c a n n e r s I n = new S c a n n e r ( a r g s [ 0 ] ) ;
690 int cual = sIn . nextInt ( );
700 I n t e g e r [ ] i n t A r r a y = new I n t e g e r [ 5 ] ;
710 I n t e g e r [ ] malos = n u l l ;
720 switch ( c u a l ) {
730 case 1 : // A r i t h m e t i c
740 System . o u t . p r i n t l n ( " Llamamos a calcula (0):"
750 + calcula (0));
760 break ;
770 case 2 : // A r r a y S t o r e
780 System . o u t . p r i n t l n ( " ArrayStoreException :" ) ;
790 g u a r d a O b j e t o ( i n t A r r a y , 0 , "abc" ) ;
800 break ;
810 case 3 : // C l a s s C a s t E x c e p t i o n
820 System . o u t . p r i n t l n ( " ClassCastExceptionException " ) ;
830 i n t e r p r e t a O b j e t o ( i n t A r r a y , 0 , "abc" ) ;
840 break ;
850 case 4 : // I l l e g a l A r g u m e n t E x c e p t i o n
860 System . o u t . p r i n t l n ( " IllegalArgumentException " ) ;
870 System . o u t . p r i n t l n ( " Cálculo de raı́z cuarta "
880 + raizCuarta ( 5.0));
890 break ;
900 case 5 : // A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n
910 int [ ] enteros = {6 ,2 ,6 ,8};
920 System . o u t . p r i n t l n ( " Llamamos a maximo (enteros , 4)="
930 + maximo ( e n t e r o s , 4 ) ) ;
940 break ;
429 Manejo de errores en ejecución

Código 9.1 Clase para causar excepciones ExcepcionesRuntimeSimples (3/3)

950 case 6 : // N e g a t i v e A r r a y S i z E x c e p t i o n
960 i n t [ ] b u e n o s = l l e n a A r r e g l o (  5 ,  1);
970 System . o u t . p r i n t l n ( b u e n o s . l e n g t h ) ;
980 break ;
990 case 7 : // N u l l P o i n t e r E x c e p t i o n
1000 System . o u t . p r i n t l n ( " Llenando arreglo nulo" ) ;
1010 l l e n a A r r e g l o ( malos , 7 ) ;
1020 break ;
1030 case 8 : // I n p u t M i s m a t c h E x c e p t i o n
1040 System . o u t . p r i n t l n ( " Leyendo enteros " ) ;
1050 leeNumeros ( ) ;
1060 break ;
1070 }
1080 } // main
1090 }

Como los métodos no vigilan las excepciones –excepto por el de ArrayInde-


xOutOfBoundsException–, y las estamos provocando, se va a tener que invocar la
ejecución del mismo por cada excepción que queramos causar. En la lı́nea 680
creamos un Scanner que se va a aplicar a una cadena, el primer argumento pa-
sado en la ejecución. En la lı́nea 680 “leemos” esa cadena para extraer el valor
del selector de casos. Revisaremos ahora algunas de las excepciones de tiempo de
ejecución.

ArithmeticException Es lanzada cuando el programa intenta hacer una operación


aritmética inválida, como la división entre enteros que tiene a 0 (cero) como
divisor4 . En el método calcula –lı́neas 70 a 110–, por ejemplo, puede presen-
tarse este tipo de excepción, si es invocado con los argumentos equivocados.
Si el método se invoca con un valor k  0 el método se ejecuta bien y no tiene
problemas. En cambio, si se le invoca con k  0, se lanzará la excepción. El
resultado de lanzar la excepción es que la Máquina Virtual de Java empieza
a buscar, en la pila de ejecución, siguiendo la cadena de llamadas de los
métodos, para ver si encuentra un manejador de la excepción; si lo encuentra,
hace lo conducente; si no lo encuentra, el programa “aborta” con un mensaje
de error que incluye la historia de llamadas –la pila de ejecución–. Un error
provocado por el procedimiento que acabamos de mostrar se muestra en la
figura 9.1.

4
Java contempla el valor infinity, por lo que una división donde el dividendo sea un número
real y el divisor 0 no lanza esta excepción, sino que entrega infinity como resultado.
9.1 Tipos de errores 430

Figura 9.1 Ejecución de AritmExc


elisa@lambda :˜/ programas$ j a v a e x c e p c i o n e s / ExcepcionesRuntimeSimples 1
E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . A r i t h m e t i c E x c e p t i o n : / by z e r o
at excepciones . ExcepcionesRuntimeSimples . c a l c u l a ( ExcepcionesRun
timeSimples . java :7)
a t e x c e p c i o n e s . E x c e p c i o n e s R u n t i m e S i m p l e s . main ( E x c e p c i o n e s R u n t i
meSimples . j a v a : 6 3 )
elisa@lambda :˜/ programas$

ArrayStoreException Es lanzada cuando el programa intenta guardar un objeto de


tipo erróneo en un arreglo. Veamos el ejemplo en las lı́neas 130 a 150 del
listado 9.1.
En la lı́nea 140, que es donde se guarda a un objeto en un arreglo, la asig-
nación es perfectamente compatible. Java no puede detectar en el momento
de compilación que va a tener un error al llamar a este método, ya que los
argumentos son de un tipo que extiende a Object, y por lo tanto permiti-
dos. La ejecución de este programa produce la salida que se muestra en la
figura 9.2.

Figura 9.2 Ejecución de ArraySE


programas$ j a v a e x c e p c i o n e s / ExcepcionesRuntimeSimples 2
ArrayStoreException :
E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . A r r a y S t o r e E x c e p t i o n : j a v a . l a n g . S t r i n g
at excepciones . ExcepcionesRuntimeSimples . guardaObjeto ( Excepcion
esRuntimeSimples . java :13)
a t e x c e p c i o n e s . E x c e p c i o n e s R u n t i m e S i m p l e s . main ( E x c e p c i o n e s R u n t i m
eSimples . java :68)
elisa@lambda :˜/ programas$

ClassCastException Es lanzada cuando el programa intenta aplicar un cast de una


clase a un objeto de otra clase, y la conversión no está permitida (no se trata
de una subclase). Al invocarse al programa con el argumento “2” se ejecuta
la lı́nea 790, que consiste de una llamada al método que se encuentra en las
lı́neas 170 a 200. Al ejecutarse la lı́nea 190 se lanza esta excepción.
Nuevamente, el programa está sintácticamente correcto ya que en un arreglo
de objetos de la clase Integer podemos guardar un objeto al que le aplique-
mos esa envoltura (lı́nea 190). Sin embargo, al pasarle como parámetro una
cadena, que es sintácticamente compatible con un objeto, el programa no
431 Manejo de errores en ejecución

puede hacer la coerción, por lo que lanza una excepción. La ejecución del
programa ClassCE la podemos ver en la figura 9.3.

Figura 9.3 Ejecución del programa ClassCE


. . . programas$ j a v a e x c e p c i o n e s / ExcepcionesRuntimeSimples 3
ClassCastExceptionException
E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . C l a s s C a s t E x c e p t i o n : j a v a . l a n g . S t r i n
g c a n n o t be c a s t t o j a v a . l a n g . I n t e g e r
a t e x c e p c i o n e s . E x c e p c i o n e s R u n t i m e S i m p l e s . i n t e r p r e t a O b j e t o ( Excep
cionesRuntimeSimples . java :18)
a t e x c e p c i o n e s . E x c e p c i o n e s R u n t i m e S i m p l e s . main ( E x c e p c i o n e s R u n t i m
eSimples . java :72)
e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $

IllegalArgumentException Esta excepción es lanzada cuando algún método de Java


o definido por el usuario detecta que un argumento no es como se esperaba,
por ejemplo, si se desea sacar la raı́z cuadrada de un número negativo. Se
encuentra ejemplificada en las lı́neas 220 a 240 en el método raizCuarta, cuya
llamada se encuentra en la lı́nea 880. La ejecución muestra algo muy similar
a lo que hemos estado mostrando, por lo que ya no aparece. Lo único que
cambia es el nombre de la excepción y las lı́neas donde es lanzada –ya no
se muestra la ejecución pues toma exactamente la misma forma que los dos
ejemplos anteriores–.
IllegalMonitorStateException Tiene que ver con sincronización de procesos. Lo vere-
mos más adelante.
IndexOutOfBoundsException Es lanzada cuando se intenta usar un elemento de un
arreglo que no existe, porque el ı́ndice dado no está en el rango dado para
los ı́ndices del arreglo. Se ejemplifica con la ejecución seleccionando el caso
5 en main. Al ejecutarse la lı́nea 300 con i valiendo 4, como el arreglo que
se pasó como parámetro tiene elementos del 0 al 3 (4 elementos), el ı́ndice 4
lanza la excepción.
NegativeArraySizeException Se lanza si es que se intenta crear un arreglo con un
número negativo de elementos. Se ejemplifica en la llamada de la lı́nea 960.
Nuevamente los parámetros son de los tipos especificados en el método, por
lo que no hay error sintáctico. Sin embargo, al no poderse construir el arreglo
en la lı́nea 440, ya que el tamaño es negativo, se lanza la excepción.
NullPointerException Se lanza si se trata de usar una referencia nula para acceder a
un objeto que no existe. Se invoca al método que ejemplifica a esta excepción
en la lı́nea 1010 y la excepción se lanza desde la lı́nea 530, al tratar de
asignarle asignar un objeto (Integer) en un lugar de un arreglo que no ha
9.1 Tipos de errores 432

sido construido. Nuevamente este error no se puede detectar sintácticamente,


pues el uso de los distintos tipos y clases es correcto.
SecurityException Se lanza cuando el proceso intenta ejecutar un método que no
se puede ejecutar por razones de seguridad. Tiene que ver con el sistema de
seguridad (SecurityManager), que no revisaremos en esta ocasión.
java.util.EmptyStackException Es lanzada cuando un programa, que utiliza la clase
Stack de Java intenta tener acceso a un elemento de la pila cuando la pila
está vacı́a.
java.util.NoSuchElementException Error relacionado con enumeraciones. Una enu-
meración es una lista de variables que asumen el orden en el que están
listadas. Cada una de las variables corresponde a una constante del tipo
dado a la enumeración.
java.util.InputMismatchException Esta excepción es lanzada cuando se intenta leer
un cierto tipo de dato pero lo que se le da es un dato de otro tipo. Por
ejemplo, se le indica a un objeto de la clase Scanner que lea un entero con
nextInt() y se encuentra caracteres que no puede interp[retar como entero,
como una letra o un sı́mbolo de puntuación antes o inmediatament3e después
del número propiamente dicho.
Hay dos aspectos ı́ntimamente relacionados con las excepciones: el primero de
ellos corresponde a las condiciones bajo las cuales una excepción es disparada o
lanzada (thrown), mientras que el otro aspecto tiene que ver con la manera que
tendrá la aplicación de reaccionar cuando una excepción es lanzada. Dependiendo
del tipo de excepción que fue lanzada, la aplicación debe reaccionar de manera
adecuada frente a ella y actuar en consecuencia. Algunas veces esta respuesta
consistirá de alguna forma en que la aplicación se recupere y siga adelante; otras
veces simplemente le permitirá a la aplicación “morir dignamente”. A la reacción
frente a una excepción se conoce como atrapar la excepción, para poder hacer algo
con ella.
En general, si algún método de alguna clase que estemos utilizando avisa que
puede lanzar una excepción, la aplicación que use esta clase deberá prepararse
para detectar si se lanzó o no la excepción, y si se lanzó reaccionar de alguna
manera frente a ella. A los enunciados que comprenden la reacción frente a la
excepción –que atrapan la excepción– es a lo que se conoce como el manejador de
la excepción.
Se recomienda enfáticamente que las excepciones de tiempo de ejecución
(RuntimeException) no sean atrapadas (atrapadas). Este tipo de excepción indica
errores de diseño en la aplicación, por lo que si se decide atraparlas esto se de-
berá hacer únicamente en una etapa de depuración del programa, con el objeto
de obtener un poco más de información sobre la causa de la excepción.
433 Manejo de errores en ejecución

Sabemos que un método es susceptible de lanzar una excepción si en el enca-


bezado del método, a continuación del paréntesis que cierra la lista de parámetros,
aparece el enunciado
throws xlista de clases de Excepcionesy
y en el cuerpo del método, o de alguno de los métodos invocados dentro de ese
cuerpo, deberá aparecer un enunciado
throw new xconstructor de una excepcióny
En el caso de las excepciones en tiempo de ejecución –RuntimeException– los
métodos que pudieran incurrir en algún error de ese tipo no tienen que avisar
que pudieran lanzar este tipo de excepciones. Asimismo, las excepciones serán
lanzadas, en su momento, implı́citamente o por el usuario, pero sin necesidad de
avisar que pudieran ocurrir.
Cuando algún enunciado o método detecta una excepción, la JVM busca en su
entorno inmediato de ejecución (llamadas en la pila de ejecución) al manejador de
la excepción. Si lo encuentra, lo ejecuta, y ya sea que procese la excepción o que
simplemente la propague (la vuelva a lanzar). Si en el entorno inmediato no se
encuentra al manejador de la excepción, la JVM sigue buscando hacia “afuera” en
la cadena de llamadas del método en cuestión a ver si encuentra a algún manejador
de la excepción. Si no lo encuentra en la aplicación se lo pasa a la JVM, que procede
a suspender el programa con el mensaje de error correspondiente y una historia
de la cadena de llamadas hasta el punto donde la excepción fue lanzada.
Si en el método en el que se lanza una excepción que no sea de tiempo de
ejecución no hay un manejador para la excepción, el método deberá incluir el
enunciado throws xexcepcióny en su encabezado.

9.2 La clase Exception

La clase Exception es una clase muy sencilla que tiene realmente muy po-
cos métodos. De hecho, únicamente tiene dos constructores que se pueden usar
en aquellas clases que hereden a Exception. La clase Throwable, superclase de
Exception, es la que cuenta con algunos métodos más que se pueden invocar desde
cualquier excepción, ya sea ésta de Java o del programador. Veamos primero los
dos constructores de Exception.
public Exception() Es el constructor por omisión. La única información que pro-
porciona es el nombre de la excepción.
9.3 Cómo detectar y atrapar una excepción 434

Construye el objeto pasándole una cadena que pro-


public Exception(String msg)
porciona información adicional a la del nombre de la clase.

La clase Throwable nos va a permitir un manejo más preciso de las excepciones.


Tiene varios tipos de métodos.

Constructores:
public Throwable() Es el constructor por omisión.
public Throwable(String msg) Da la oportunidad de construir el objeto con infor-
mación que se transmite en la cadena msg.

Métodos de la pila (stack) de ejecución:


public native Throwable fillInStackTrace()Coloca en el objeto una copia de la cadena
dinámica de la pila de ejecución en ese momento.
public void printStackTrace() Escribe en la pantalla el contenido de la pila de ejecu-
ción, respecto a la cadena de llamadas que llevaron al método que está abor-
tando.
public void printStackTrace(PrintStream s) Lo mismo que el anterior, pero da la op-
ción de escribir a un archivo en disco.

Métodos de acceso a campos:


Devuelve la cadena con que la excepción fue creada. Si
public String getMessage()
fue creada con el constructor por omisión devuelve null.

Método descriptivo:
Regresa en una cadena el nombre de la clase a la que per-
public String toString()
tenece y la cadena con la que fue creada, en su caso.
Como la clase Exception extiende a la clase Throwable, cualquier excepción
que nosotros declaremos que extienda a Exception contará con los métodos de
Throwable.
Las excepciones de tiempo de ejecución siempre van a imprimir la cadena
producida por toString() y la pila de ejecución producida por printStackTrace().
De esa manera tenemos información muy puntual de cuál fue el tipo de error y
dónde exactamente se presentó.
435 Manejo de errores en ejecución

9.3 Cómo detectar y atrapar una excepción

Como ya mencionamos, una aplicación común y corriente puede lanzar dis-


tintas excepciones de tiempo de ejecución. Estas excepciones no tienen que ser
vigiladas y detectadas, sino que pasan a la JVM, quien suspende la ejecución del
programa.

Sin embargo, también pueden presentarse excepciones que no son de las lla-
madas “de tiempo de ejecución” (RuntimeException) y que sı́ deben ser vigila-
das y manejadas de alguna manera. Entre ellas podemos mencionar las que son
lanzadas en relación con procesos de entrada y salida (IOException); cuando du-
rante la ejecución de una aplicación no se encuentra una clase que se debe cargar
(ClassNotFoundException); cuando se pretende hacer una copia de un objeto que no
es duplicable (CloneNotSupportedException); cuando se da alguna interrupción en
un proceso que se está ejecutando (InterruptedException); y algunas más. Además,
el usuario puede declarar sus propias clases de excepciones, que pueden extender
a una clase particular de excepciones o a la clase genérica Exception.

La detección y manejo de una excepción se lleva a cabo en un bloque que toma


la forma que se muestra en la figura 9.4 en la siguiente página5 .

5
En este caso subrayamos lo que forma parte del enunciado en Java para distinguirlo de lo
que marca repetición de algún segmento.
9.3 Cómo detectar y atrapar una excepción 436

Figura 9.4 Detección y manejo de excepciones


Sintaxis:
try {
xenunciados
 donde puede ser lanzada la excepcióny
} catch ( xtipo de excepcióny xidentify ) {
xEnunciados
que reaccionan frente a la excepcióny
}
Semántica:
Se pueden agrupar tantos enunciados como se desee en la cláusula try,
por lo que al final de ella se puede estar reaccionando frente a distintas
excepciones. Se va a lanzar únicamente la primera excepción que se presente,
pues al ocurrir el error no se continúa ejecutando el bloque try. De manera
similar a como se hace en un switch, se elige uno y solo un manejador de
excepciones, utilizando la primera excepción que califique con ser del tipo
especificado en las cláusulas catch.

La parte que aparece a continuación de cada catch corresponde al manejador


de cada clase de excepción que se pudiera presentar en el cuerpo del try. El tipo
de excepción puede ser Exception, en cuyo caso cualquier tipo de excepción va
a ser atrapada y manejada dentro de ese bloque. En general, una excepción que
extiende a otra puede ser atrapada por cualquier manejador para cualquiera de sus
superclases. Una vez que se lista el manejador para alguna superclase en uno de
los bloques catch, no tiene sentido listar manejadores para las subclases después
de él.
La manera como se ejecuta un bloque try en el que se pudiera lanzar una
excepción es la siguiente:
La JVM entra a ejecutar el bloque correspondiente.
Como cualquier bloque en Java, todas las declaraciones que se hagan dentro
del bloque son visibles únicamente dentro del bloque.
Se ejecuta el bloque enunciado por enunciado.
En el momento en que se presenta una excepción, la ejecución del bloque se
interrumpe y la ejecución prosigue buscando a un manejador de la excepción.
La ejecución verifica los manejadores, uno por uno y en orden, hasta que
encuentre el primero que pueda manejar la excepción. Si ninguno de los
manejadores puede manejar la excepción que se presentó, sale del registro de
activación actual a aquel desde el cual fue invocado buscando un manejador
437 Manejo de errores en ejecución

de la excepción, y ası́ sucesivamente hasta que encuentra uno o bien la JVM


aborte el programa porque no hay en la pila de ejecución ningún método en
el que se maneje la excepción.
Si encuentra un manejador adecuado, se ejecuta el bloque correspondiente
al manejador.
Si no se vuelve a lanzar otra excepción, la ejecución continúa en el enunciado
que sigue al último manejador.

El programa que se encuentra en el listado 9.2 atrapa las excepciones posibles


en el bloque del try mediante una cláusula catch para la superclase Exception. Una
vez dentro del manejador, averigua cuál fue realmente la excepción que se disparó,
la reporta y sigue adelante con la ejecución del programa, que en este caso consiste
únicamente en seguir ejecutando main imprimiendo un mensaje de despedida.

Código 9.2 Manejo de una excepción a través de la superclase CatchExc (1/2)

10 package e x c e p c i o n e s ;
20 import j a v a . i o . ∗ ;
30 import j a v a . u t i l . S c a n n e r ;
40 import j a v a . u t i l . I n p u t M i s m a t c h E x c e p t i o n ;
9.3 Cómo detectar y atrapar una excepción 438

Código 9.2 Manejo de una excepción a través de la superclase CatchExc (2/2)

60 p u b l i c c l a s s CatchExc {
70 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
80 int k = 0;
90 int c ;
100 int i = 3;
110 i n t [ ] e n t e r o s = new i n t [ 3 ] ;
120 S c a n n e r s c a n = new S c a n n e r ( System . i n ) ;
130 try {
140 System . o u t . p r i n t l n ( "Dame un entero para trabajar " ) ;
150 w h i l e ( ( c = System . i n . r e a d ( ) ) >= ’0’ && c <= ’9’ )
160 k = k ∗ 10 + ( c  ( i n t ) ’0’ ) ;
170 System . o u t . p r i n t l n ( "Lei el valor " + k ) ;
180 c = 1000/ k ;
190 System . o u t . p r i n t l n ( "El valor final de c es " + c ) ;
200 System . o u t . p r i n t l n ( "Dame otro entero :" ) ;
210 c = scan . n e x t I n t ( ) ;
220 i f ( c != 0 )
230 System . o u t . p r i n t l n ( " División :" + ( k / c ) ) ;
240 enteros [ i ] = c ;
250 } catch ( E x c e p t i o n e ) {
260 System . o u t . p r i n t l n ( "La excepción es de la clase : \n\t"
270 + e . getClass ());
280 }
290 System . o u t . p r i n t l n ( "A punto de salir normalmente "
300 + "del metodo main" ) ;
310 }
320 }

Este programa atrapa cualquier excepción que se presente en el bloque try y la


maneja, permitiendo que el programa termine normalmente. Se pueden presentar
las siguientes excepciones:
Lı́nea 180: Puede haber una división entre 0 (ArithmeticaException) pues la k es
calculada en el programa y depende de los datos de entrada.
Lı́nea 210: El usuario puede proporcionar al dispositivo de lectura (Scanner) algo
que no sea un entero.
Lı́nea 240: Si la ejecución llega a esta lı́nea se produce el error de ı́ndice inválido,
pues el arreglo sólo tiene tres elementos.
En las figuras 9.5 a 9.7 mostramos ejecuciones en los que se presenta cada uno
de estos errores (se lanza la excepción).
En el primer caso –figura 9.5– cuando la aplicación pide el segundo entero se
439 Manejo de errores en ejecución

teclean caracteres –lı́neas 60 y 70–, por lo que se lanza una excepción que indica
que la entrada no coincide con lo esperado –InputMismatchException–.
En el segundo caso –figura 9.6– se proporciona el valor 0 para la variable k, lo
que provoca una división entre 0 y el lanzamiento de la excepción correspondiente,
ArithmeticException.

Figura 9.5 Excepciones de tiempo de ejecución atrapadas con una superclase (A)
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc
20 Dame un e n t e r o p a r a t r a b a j a r
30 35
40 L e i e l v a l o r 35
50 E l v a l o r f i n a l de c e s 28
60 Dame o t r o e n t e r o :
70 lmn
80 La e x c e p c i ó n e s de l a c l a s e :
90 class java . u t i l . InputMismatchException
100 A punto de s a l i r normalmente d e l metodo main
110 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $

Figura 9.6 Excepciones de tiempo de ejecución atrapadas con una superclase (B)
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc
20 Dame un e n t e r o p a r a t r a b a j a r
30
40 Lei el valor 0
50 La e x c e p c i ó n e s de l a c l a s e :
60 class java . lang . ArithmeticException
70 A punto de s a l i r normalmente d e l metodo main
80 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $

En el tercer caso los datos se proporcionan bien pero se intenta acceder al


elemento con ı́ndice 3 de un arreglo, cuyos ı́ndices válidos son del 0 al 2, por lo
que se lanza la excepción correspondiente.
Figura 9.7 Excepciones de tiempo de ejecución atrapadas con una superclase (C)
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc
20 Dame un e n t e r o p a r a t r a b a j a r
30 45
40 L e i e l v a l o r 45
50 E l v a l o r f i n a l de c e s 22
60 Dame o t r o e n t e r o :
70 10
80 D i v i s i ó n : 4
90 La e x c e p c i ó n e s de l a c l a s e :
100 class java . lang . ArrayIndexOutOfBoundsException
110 A punto de s a l i r normalmente d e l metodo main
120 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
9.3 Cómo detectar y atrapar una excepción 440

Si queremos que el programa se interrumpa después de lanzada una excepción,


podemos “relanzarla” en el manejador –bloque catch–. En este caso, el método
en el que esto se hace debe avisar que se va a lanzar una excepción, ya que
habrá una salida brusca del mismo. En el listado 9.3 modificamos la clase CatchExc
–CatchExc1– para que relance la excepción y termine la ejecución. En la figura 9.8
en la página opuesta vemos la ejecución de esta nueva clase.

Código 9.3 La excepción es atrapada y relanzada CatchExc1


10 package e x c e p c i o n e s ;
20 import j a v a . i o . ∗ ;
30 import j a v a . u t i l . S c a n n e r ;
40
50 p u b l i c c l a s s CatchExc1 {
60
70 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
80 throws E x c e p t i o n {
90 int k = 0;
100 int c ;
110 int i = 3;
120 i n t [ ] e n t e r o s = new i n t [ 3 ] ;
130 S c a n n e r s c a n = new S c a n n e r ( System . i n ) ;
140 try {
150 System . o u t . p r i n t l n ( "Dame un entero para trabajar " ) ;
160 w h i l e ( ( c = System . i n . r e a d ( ) ) >= ’0’ && c <= ’9’ )
170 k = k ∗ 10 + ( c  ( i n t ) ’0’ ) ;
180 System . o u t . p r i n t l n ( "Lei el valor " + k ) ;
190 c = 1000/ k ;
200 System . o u t . p r i n t l n ( "El valor final de c es " + c ) ;
210 System . o u t . p r i n t l n ( "Dame otro entero :" ) ;
220 c = scan . n e x t I n t ( ) ;
230 i f ( c != 0 ) {
240 System . o u t . p r i n t l n ( " División :" + ( k / c ) ) ;
250 }
260 enteros [ i ] = c ;
270 } catch ( E x c e p t i o n e ) {
280 C l a s s en = e . g e t C l a s s ( ) ;
290 System . o u t . p r i n t l n ( "La excepción es de la clase : \n\t"
300 + en ) ;
310 throw ( ( E x c e p t i o n ) en . n e w I n s t a n c e ( ) ) ;
320 }
330 System . o u t . p r i n t l n ( "A punto de salir normalmente "
340 + "del metodo main" ) ;
350 }
360 }

Como la clase a la que pertenece la excepción se obtiene con el método get-


Class() de la clase Class, esto se hace durante ejecución. Si bien se maneja la
excepción mediante la cláusula catch de la lı́nea 270, se vuelve a relanzar en la
441 Manejo de errores en ejecución

lı́nea 310, por lo que la lı́nea 330 del programa, que se encuentra a continuación del
bloque de la excepción, ya no se ejecuta. En este ejemplo usamos varios métodos
de la clase Class, como getClass(), que regresa el “tipo” de la clase, y newIns-
tance(), que construye un objeto de la clase especificada. La clase Class contiene
muchos métodos que se usan para obtener las descripciones de cualquier clase que
se encuentre disponible.
Figura 9.8 Ejecución con relanzamiento de la excepción
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc1
20 Dame un e n t e r o p a r a t r a b a j a r
30 0
40 Lei el valor 0
50 La e x c e p c i ó n e s de l a c l a s e :
60 class java . lang . ArithmeticException
70 E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . A r i t h m e t i c E x c e p t i o n
80 a t sun . r e f l e c t . N a t i v e C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e 0 ( N a t i v e
90 Method )
100 a t sun . r e f l e c t . N a t i v e C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e ( N a t i v e C
110 onstructorAccessorImpl . java :57)
120 a t sun . r e f l e c t . D e l e g a t i n g C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e ( Del
130 egatingConstructorAccessorImpl . java :45)
140 at java . lang . r e f l e c t . Constructor . newInstance ( Constructor . java :532)
150 at java . lang . Class . newInstance0 ( Class . java :372)
160 at java . lang . Class . newInstance ( Class . java :325)
170 a t e x c e p c i o n e s . CatchExc1 . main ( CatchExc1 . j a v a : 3 1 )
180 e l i s a @ e l i s a  d e s k t o p : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $

Un método, ya sea de las bibliotecas de clases de Java o del programador,


puede lanzar una excepción, y ésta puede ser, nuevamente, una excepción creada
por el programador o de las clases de Java. Por ejemplo, si deseamos que no haya
divisiones por 0 aún entre reales, podrı́amos tener las clases que se muestran en
los listados 9.4 y 9.5 en la siguiente página.

Código 9.4 Implementación de excepciones propias DivPorCeroException

10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s D i v P o r C e r o E x c e p t i o n extends A r i t h m e t i c E x c e p t i o n {
30 public DivPorCeroException () {
40 super ( ) ;
50 }
60 p u b l i c D i v P o r C e r o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }
9.3 Cómo detectar y atrapar una excepción 442

Código 9.5 Detección de excepciones propias DivPorCeroUso

10 package e x c e p c i o n e s ;
20 import j a v a . u t i l . S c a n n e r ;
30 p u b l i c c l a s s D i v P o r Ce r o U s o {
40 public static float sqrt ( float x )
50 throws D i v P o r C e r o E x c e p t i o n {
60 i f ( x < 0)
70 throw new D i v P o r C e r o E x c e p t i o n ( "Se pide raiz de numero "
80 + " negativo !" ) ;
90 else
100 r e t u r n ( ( f l o a t ) Math . s q r t ( x ) ) ;
110 }
120
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 // Aca l e e r i a m o s v a l o r p a r a r en v e z de c a l c u l a r l o a l a z a r
150 f l o a t r = ( ( f l o a t ) Math . random ( ) ∗ 1 0 0 0 0 ) ;
160 f l o a t o t r o = ( f l o a t ) ( Math . random ( ) + . 5 ) ;
170 System . o u t . p r i n t l n ( "Otro: "+ o t r o ) ;
180 int signo = ( otro > 1 ? 0 : 1 );
190 i f ( s i g n o == 0 )
200 r = r ;
210 // Aca t e r m i n a m o s de o b t e n e r un v a l o r p a r a r
220 try {
230 System . o u t . p r i n t l n ( "La raiz cuadrada de "+ r + " es "
240 + sqrt ( r ));
250 }
260 catch ( D i v P o r C e r o E x c e p t i o n e ) {
270 System . o u t . p r i n t l n ( e . g e t M e s s a g e ( ) ) ;
280 System . e x i t (  1);
290 }
300 }
310 }

Como la excepción que construimos, DivPorCeroException, extiende a Arithme-


ticException y esta última no debe ser vigilada, la invocación a sqrt no tendrı́a
que hacerse dentro de un bloque try. Asimismo, el método sqrt no tendrı́a por
qué avisar que va a lanzar una excepción. Sin embargo, en este ejemplo usamos
ambos enunciados porque queremos ejemplificar su uso.
Supongamos ahora que la excepción hereda directamente a Exception o a cual-
quiera que no sea RuntimeException, como se muestra en el listado 9.6 en la página
opuesta. En este caso, si en el archivo DivPorCeroUso.java comentamos las lı́neas
50, 220 y 250 a 290, el método lanza una excepción que no es de la clase RuntimeEx-
ception, por lo que no puede dejar de avisar que va a lanzar una excepción: al
compilar a la clase DivPorCeroUso, suponiendo que DivPorCeroException extiende
a Exception, da el error de compilación que se muestra en la figura 9.9.
443 Manejo de errores en ejecución

Código 9.6 Excepción que no se declara subclase de RuntimeException DivPorCero2Exception

10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s D i v P o r C e r o 2 E x c e p t i o n extends E x c e p t i o n {
30 public DivPorCero2Exception () {
40 super ( ) ;
50 }
60 p u b l i c D i v P o r C e r o 2 E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }

Figura 9.9 Excepción que hereda de Exception


10 CompileServer output :
20 c l a s s p a t h/home/ e l i s a / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s  s o u r c e p a t h /home
30 / e l i s a / ICC1 : / home/ e l i s a / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s  t a r g e t 1 . 6 g /
40 home/ e l i s a / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s / e x c e p c i o n e s / D i v P o r Ce r o U s o . j a v a
50 /home/ e l i s a / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s / e x c e p c i o n e s / D i v P o r Ce r o U s o . j a
60 va : 8 : u n r e p o r t e d e x c e p t i o n e x c e p c i o n e s . D i v P o r C e r o E x c e p t i o n ; must be cau
70 g h t o r d e c l a r e d t o be thrown
80 throw new D i v P o r C e r o E x c e p t i o n ( "Se pide raiz de numero negat
90 ivo!" ) ;
100 ˆ
110 1 error
120
130 C o m p i l a t i o n e x i t e d a b n o r m a l l y w i t h code 1 a t Thu May 26 1 0 : 5 6 : 5 1

Una vez corregido esto, la llamada al método sqrt tiene que aparecer dentro
de un bloque try que se encargue de detectar la excepción que lanza el método.
Como la excepción se maneja totalmente dentro del método en cuestión, que
en este caso es main, la excepción no se propaga hacia afuera de main, por lo que
el método no tiene que avisar que pudiera lanzar una excepción.
Como ya mencionamos antes, las excepciones se pueden lanzar en cualquier
momento: son simplemente un enunciado más. Por supuesto que un uso racional
de ellas nos indica que las deberemos asociar a situaciones no comunes o crı́ticas,
pero esto último tiene que ver con la semántica de las excepciones, no con la
sintaxis.
Tal vez el ejemplo del listado 9.5 en la página opuesta no muestre lo útil que
pueden ser las excepciones, porque redefinen de alguna manera una excepción
que la JVM lanzarı́a de todos modos. Pero supongamos que estamos tratando de
armar una agenda telefónica, donde cada individuo puede aparecer únicamente
una vez, aunque tenga más de un teléfono. Nuestros métodos de entrada, al tratar
de meter un nombre, detectan que ese nombre con la dirección ya está registrado.
En términos generales, esto no constituye un error para la JVM, pero si para el
9.3 Cómo detectar y atrapar una excepción 444

contexto de nuestra aplicación. Una manera elegante de manejarlo es a través de


excepciones, como se muestra en los listados 9.7 a 9.9.

Código 9.7 Excepciones del programador (I) RegDuplicadoException

10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s R e g D u p l i c a d o E x c e p t i o n extends E x c e p t i o n {
30 public RegDuplicadoException () {
40 super ( ) ;
50 }
60 p u b l i c R e g D u p l i c a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }

Código 9.8 Uso de excepciones del programador (I) RegNoEncontradoException

10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s R e g N o E n c o n t r a d o E x c e p t i o n extends E x c e p t i o n {
30 public RegNoEncontradoException ( ) {
40 super ( ) ;
50 }
60 p u b l i c R e g N o E n c o n t r a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }

Código 9.9 Excepciones del programador y su uso (II) BaseDeDatos (1/2)

10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s BaseDeDatos {
30 i n t numRegs ;
40 ...
50 public void agrega ( R e g i s t r o reg )
60 throws R e g D u p l i c a d o E x c e p t i o n {
70 ...
80 i f ( actual . equals ( reg ))
90 throw new R e g D u p l i c a d o E x c e p t i o n ( r e g . nombre ) ;
100 ...
110 }
445 Manejo de errores en ejecución

Código 9.9 Excepciones del programador y su uso (II) BaseDeDatos (2/2)

120 ...
130 public void e l i m i n a ( R e g i s t r o reg )
140 throws R e g N o E n c o n t r a d o E x c e p t i o n {
150 ...
160 i f ( a c t u a l == n u l l )
170 throw new R e g N o E n c o n t r a d o E x c e p t i o n ( r e g . nombre ) ;
180 ...
190 }
200 ...
210 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
220 ...
230 w h i l e ( o p c i o n != 0 ) {
240 try {
250 switch ( opcion ) {
260 case 1 : a g r e g a ( r e g ) ;
270 reportaAgregado ( ) ;
280 break ;
290 case 2 : e l i m i n a ( r e g ) ;
300 reportaEliminado ();
310 break ;
320 ...
330 ...
340 } // s w i t c h
350 } // t r y
360 catch ( R e g D u p l i c a d o E x c e p t i o n e ) {
370 // p r o d u c e un manejo a d e c u a d o de l a r e p e t i c i ó n
380 System . o u t . p r i n t l n ( "El registro de: "
390 + e . getMessage ( )
400 + "ya existe , por lo que no se agregó " ) ;
410 } // R e g D u p l i c a d o E x c e p t i o n
420 catch ( R e g N o E n c o n t r a d o E x c e p t i o n e ) {
430 // P ro d uc e un manejo ad e c u ad o a l no e n c o n t r a r a l
440 // nombre
450 System . o u t . p r i n t l n ( "El registro de: "
460 + e . getMessage ( )
470 + "no se encontró , por lo que no se eliminó " ) ;
480 } // R e g N o E n c o n t r a d o E x c e p t i o n
490 } // w h i l e
500 ...
510 } // main
520 } // c l a s s

El listado 9.9 en la página opuesta hace uso del hecho de que cuando en un
bloque se presenta una excepción, la ejecución salta a buscar el manejador de la
excepción y deja de ejecutar todo lo que esté entre el punto donde se lanzó la
9.4 Las clases que extienden a Exception 446

excepción y el manejador seleccionado. Como tanto agrega como elimina lanzan


excepciones, su invocación tiene que estar dentro de un bloque try –que va de la
lı́nea 240 a la lı́nea 350–. Si es que estos métodos lanzan la excepción, ya sea en la
lı́nea 260 o 290, ya no se ejecuta la lı́nea 270 en el primer caso y la lı́nea 300 en el
segundo. Por lo tanto se está dando un control adecuado del flujo del programa,
utilizando para ello excepciones.
Otra caracterı́stica que tiene este segmento de aplicación es que como el bloque
try está dentro de una iteración, y si es que se hubiere lanzado una excepción en
alguno de los métodos invocados, una vez que se llegó al final del bloque try y
habiéndose o no ejecutado alguno de los manejadores de excepciones asociados al
bloque try, la ejecución regresa a verificar la condición del ciclo, logrando de hecho
que el programa no termine por causa de las excepciones. Esta forma de hacer las
cosas es muy común. Supongamos que le pedimos al usuario que teclee un número
entero y se equivoca. Lo más sensato es volverle a pedir el dato al usuario para
trabajar con datos adecuados, en lugar de abortar el programa.

9.4 Las clases que extienden a Exception

Hasta ahora, cuando hemos declarado clases que extienden a Exception no


hemos agregado ninguna funcionalidad a las mismas. No sólo eso, sino que úni-
camente podemos usar sus constructores por omisión, los que no tienen ningún
parámetro.
El constructor por omisión siempre nos va a informar del tipo de excepción que
fue lanzado (la clase a la que pertenece), por lo que el constructor de Exception
que tiene como parámetro una cadena no siempre resulta muy útil. Sin embargo,
podemos definir una clase tan compleja como queramos, con los parámetros que
queramos en los constructores. Únicamente hay que recordar que si definimos
alguno de los constructores con parámetros, automáticamente perdemos acceso
al constructor por omisión. Claro que siempre podemos invocar a super() en los
constructores definidos en las subclases.
Podemos, en las clases de excepciones creadas por el usuario, tener más méto-
dos o información que la que nos provee la clase Exception o su superclase
Throwable. Por ejemplo, la clase RegNoEncontradoException que diseñamos para
la base de datos pudiera proporcionar más información al usuario que simplemen-
te el mensaje de que no encontró al registro solicitado; podrı́a proporcionar los
registros inmediato anterior e inmediato posterior al usuario. En ese caso, deberı́a
poder armar estos dos registros. Para ello, podrı́amos agregar a la clase dos cam-
447 Manejo de errores en ejecución

pos, uno para cada registro, y dos métodos, el que localiza al elemento inmediato
anterior en la lista y el que localiza al inmediato posterior. En los listados 9.10
y 9.11 podemos ver un bosquejo de cómo se lograrı́a esto.

Código 9.10 Definición de Excepciones propias RegNoEncontradoException (1/2)

10 p u b l i c c l a s s R e g N o E n c o n t r a d o E x c e p t i o n extends E x c e p t i o n {
20 p r i v a t e R e g i s t r o regAnt , r e g P o s t ;
30 public RegNoEncontradoException ( ) {
40 super ( ) ;
50 }
60 p u b l i c R e g N o E n c o n t r a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 public RegNoEncontradoException ( R e g i s t r o a n t e r i o r ,
100 Registro actual ) {
110 regAnt = b u s c a A n t e r i o r ( padre ) ;
120 regPost = buscaPosterior ( actual ) ;
130 }
140 private Registro buscaAnterior ( Registro a n t e r i o r ) {
150 ...
160 }
170 private Registro buscaPosterior ( Actual ) {
180 ...
190 }
200 p u b l i c R e g i s t r o daRegAnt ( ) {
210 return regAnt ;
220 }
230 p u b l i c R e g i s t r o daRegPost ( ) {
240 return regPost ;
250 }
260 }

Código 9.11 Definición de excepciones propias (ejemplo) Ejemplo (1/2)

10 p u b l i c c l a s s E j e m p l o {
20 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
30 ...
40 try {
50 i f ( a c t u a l . s i g == n u l l ) {
60 throw new
70 RegNoEncontradoException ( a c t u a l . g e t A n t e r i o r ( ) ,
80 actual );
90 } // f i n de i f
100 ...
110 } // f i n de t r y
120 catch ( R e g N o E n c o n t r a d o E x c e p t i o n e ) {
9.4 Las clases que extienden a Exception 448

Código 9.11 Definición de excepciones propias (ejemplo) Ejemplo (2/2)

130 System . o u t . p r i n t l n ( "El registro debió encontrarse "


140 +" entre \n" ) ;
150 System . o u t . p r i n t l n ( "***" + e . daRegAnt ( ) . daNombre ( )
160 + "***\n" + " y\n" ) ;
170 System . o u t . p r i n t l n ( "***"
180 + e . daRegPost ( ) . daNombre ( )
190 + "***" ) ;
200 } // f i n de c a t c h
210 ...
220 } // main
230 } // c l a s s

En las lı́neas 60 a 80 tenemos un constructor adicional declarado para nuestra


clase que extiende a Exception. Como se puede ver en las lı́neas 130 a 170, el
manejador de la excepción hace uso de los campos y métodos declarados en la
excepción, y que son llenados por el constructor, para proveer más información al
usuario de la situación presente en el momento de la excepción. Haciéndolo de esta
manera, en el momento de lanzar la excepción se puede invocar un constructor
que recoja toda la información posible del contexto en el que es lanzada, para
reportar después en el manejador.
Únicamente hay que recordar que todas aquellas variables que sean declaradas
en el bloque try no son accesibles desde fuera de este bloque, incluyendo a los
manejadores de excepciones. Insistimos: si se desea pasar información desde el
punto donde se lanza la excepción al punto donde se maneja, lo mejor es pasarla
en la excepción misma. Esto último se consigue redefiniendo y extendiendo a la
clase Exception.
Además de la información que logremos guardar en la excepción, tenemos
también los métodos de Throwable, como el que muestra el estado de los registros
de activación en la pila, o el que llena esta pila en el momento inmediato anterior
a lanzar la excepción. Todos estos métodos se pueden usar en las excepciones
creadas por el programador.
Veamos en los listados 9.12 y 9.13 otro ejemplo de declaración y uso de ex-
cepciones creadas por el programador. En este ejemplo se agrega un constructor
y un atributo que permiten a la aplicación recoger información respecto al orden
en que se lanzan las excepciones y el contexto en el que esto sucede.
449 Manejo de errores en ejecución

Código 9.12 Excepciones creadas por el programador MiExcepcion2

10 c l a s s M i E x c e p c i o n 2 extends E x c e p t i o n {
20 private int i ;
30 public MiExcepcion2 () {
40 super ( ) ;
50 }
60 p u b l i c M i E x c e p c i o n 2 ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 p u b l i c M i E x c e p c i o n 2 ( S t r i n g msg , i n t x ) {
100 super ( msg ) ;
110 i = x;
120 }
130 public int val () {
140 return i ;
150 }
160 }

Código 9.13 Uso de excepciones creadas por el programador CaracteristicasExtra (1/2)

10 p u b l i c c l a s s C a r a c t e r i s t i c a s E x t r a {
20 p u b l i c s t a t i c v o i d f ( ) throws M i E x c e p c i o n 2 {
30 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde f()" ) ;
40 throw new M i E x c e p c i o n 2 ( ) ;
50 }
60 p u b l i c s t a t i c v o i d g ( ) throws M i E x c e p c i o n 2 {
70 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde g()" ) ;
80 throw new M i E x c e p c i o n 2 ( "Se originó en g()" ) ;
90 }
100 p u b l i c s t a t i c v o i d h ( ) throws M i E x c e p c i o n 2 {
110 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde h()" ) ;
120 throw new M i E x c e p c i o n 2 ( "Se originó en h()" , 4 7 ) ;
130 }
140 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
150 try {
160 f ();
170 }
180 catch ( M i E x c e p c i o n 2 e ) {
190 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
200 }
210 try {
220 g();
230 }
240 catch ( M i E x c e p c i o n 2 e ) {
250 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
260 }
9.4 Las clases que extienden a Exception 450

Código 9.14 Uso de excepciones creadas por el programador CaracteristicasExtra (2/2)

270 try {
280 h();
290 }
300 catch ( M i E x c e p c i o n 2 e ) {
310 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
320 System . e r r . p r i n t l n ( "e.val () = " + e . v a l ( ) ) ;
330 }
340 }
350 }

En esta aplicación se muestra el uso de distintos constructores, las invocaciones


a los métodos de Throwable y la extensión de la información que provee la clase
agregando campos. El resultado de la ejecución se puede ver en la figura 9.10.

Figura 9.10 Ejecución de CaracteristicasExtra


e l i s a @ l a m b d a . . . ICC1 / p r o g s / e x c e p c i o n e s % j a v a C a r a c t e r i s t i c a s E x t r a
Lanzando M i E x c e p c i o n 2 d e s d e f ( )
MiExcepcion2
at C a r a c t e r i s t i c a s E x t r a . f ( C a r a c t e r i s t i c a s E x t r a . java :23)
a t C a r a c t e r i s t i c a s E x t r a . main ( C a r a c t e r i s t i c a s E x t r a . j a v a : 3 8 )
Lanzando M i E x c e p c i o n 2 d e s d e g ( )
M i E x c e p c i o n 2 : Se o r i g i n ó en g ( )
at C a r a c t e r i s t i c a s E x t r a . g( C a r a c t e r i s t i c a s E x t r a . java :28)
a t C a r a c t e r i s t i c a s E x t r a . main ( C a r a c t e r i s t i c a s E x t r a . j a v a : 4 4 )
Lanzando M i E x c e p c i o n 2 d e s d e h ( )
M i E x c e p c i o n 2 : Se o r i g i n ó en h ( )
at C a r a c t e r i s t i c a s E x t r a . h( C a r a c t e r i s t i c a s E x t r a . java :33)
a t C a r a c t e r i s t i c a s E x t r a . main ( C a r a c t e r i s t i c a s E x t r a . j a v a : 5 0 )
e . v a l ( ) = 47

Recalcando lo que ya vimos respecto a excepciones, notamos varias cosas en


este listado:
Los métodos f(), g() y h() tienen que avisar que lanzan una excepción, ya
que MiExcepcion2 no hereda de RuntimeException y por lo tanto se debe
vigilar cuando se ejecute cualquiera de estos tres métodos. Vale la pena decir
que aunque el lanzamiento de la excepción fuera condicional, de cualquier
manera el método tendrı́a que avisar que existe la posibilidad de que lance
la excepción.
Como los métodos lanzan excepciones, cada uno de ellos tiene que ser invo-
cado en un bloque try.
451 Manejo de errores en ejecución

Como el bloque try consiste únicamente de la invocación al método, una vez


ejecutado el manejador de la excepción que se encuentra a continuación del
respectivo catch, la ejecución continúa en la siguiente lı́nea de código. Es
por ello que aunque se lancen las excepciones, la ejecución continúa una vez
ejecutado el manejador.
Si alguno de los métodos lanzara alguna otra excepción, el compilador exi-
girı́a que hubiera un manejador por cada tipo de excepción. Se puede atrapar
excepciones usando superclases, pero cada clase de excepción lanzada por
un método tiene que tener su manejador propio o uno que se refiera a la
superclase.
Si un método lanza una excepción y no la atrapa en el mismo método, su
encabezado tiene que especificar que lanza aquellas excepciones que no sean
atrapaDas en el mismo método.

9.4.1. Relanzamiento de excepciones

Muchas veces el manejador de una excepción hace algo de administración de


la clase y después de esto simplemente vuelve a lanzar la excepción. Si se le
va a pedir a la excepción que reporte el punto donde estaba la ejecución en el
momento en que fue lanzada la excepción –usando printStackTrace– la excepción
lanzada va a tener registro del punto donde fue creada, no del punto desde donde
es finalmente lanzada. Para que la excepción actualice su información respecto a
la pila de ejecución se utiliza el método fillInStackTrace al momento de relanzar la
excepción; esto va a hacer que la pila refleje el último punto donde la excepción
fue lanzada y no donde fue creada.

9.5 El enunciado finally

Cuando tenemos un programa en el que estamos vigilando el lanzamiento de


excepciones, vamos a tener código que, por encontrarse después del punto donde
se lanzó la excepción y dentro del bloque try, no va a ser ejecutado. Por ejemplo,
si estoy tratando de asignarle un valor a una variable y la ejecución no pasa por
ese enunciado porque antes se lanzó una excepción.
A continuación de los bloques correspondientes a atrapar las excepciones –los
9.5 El enunciado finally 452

bloques catch– podemos escribir un bloque de enunciados que se van a ejecutar


ya sea que se haya lanzado una excepción o no en el bloque try. La cláusula finally
siempre se ejecuta, no importa que se haya lanzado o no una excepción. Veamos
un ejemplo muy sencillo en el listado 9.16.
Código 9.15 Ejemplo con la cláusula finally (Excepción) TresException

10 c l a s s T r e s E x c e p t i o n extends E x c e p t i o n {
20 public TresException () {
30 super ( ) ;
40 }
50 p u b l i c T r e s E x c e p t i o n ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }

Código 9.16 Ejemplo con la cláusula finally (uso) FinallyTrabaja

90 p u b l i c c l a s s F i n a l l y T r a b a j a {
100 s t a t i c int cuenta = 0;
110 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
120 while ( true ) {
130 try {
140 // Post  i n c r e m e n t o . Es c e r o l a p r i m e r a v e z
150 i f ( c u e n t a++ == 0 ) {
160 throw new T r e s E x c e p t i o n ( ) ;
170 }
180 System . o u t . p r i n t l n ( "No hubo excepción " ) ;
190 }
200 catch ( T r e s E x c e p t i o n e ) {
210 System . e r r . p r i n t l n ( " TresException " ) ;
220 }
230 finally {
240 System . e r r . p r i n t l n ( "En la cláusula finally " ) ;
250 i f ( c u e n t a == 2 ) {
260 break ; // s a l d e l w h i l e
270 }
280 }
290 }
300 }
310 }

Como se puede ver la salida de la ejecución de este algoritmo en la figura 9.11


en la página opuesta, el mensaje mandado por el bloque finally se imprime siempre,
sin importar si hubo o no excepción.
453 Manejo de errores en ejecución

Figura 9.11 Ejecución de FinallyTrabaja


e l i s a @ l a m b d a . . . ICC1 / p r o g s / e x c e p c i o n e s % j a v a F i n a l l y T r a b a j a
TresException
En l a c l á u s u l a f i n a l l y
No hubo e x c e p c i ó n
En l a c l á u s u l a f i n a l l y

Es interesante también notar cómo, aunque se lance una excepción, como el


bloque try está dentro de una iteración, al salir de ejecutar todo el bloque asociado
a la excepción, la ejecución continúa con el while.
finally funciona como una tarea que sirve para dar una última pasada al código,
de tal manera de garantizar que todo quede en un estado estable. No siempre es
necesario, ya que Java cuenta con recolección automática de basura y destructores
de objetos también automáticos. Sin embargo, se puede usar para agrupar tareas
que se desean hacer, por ejemplo en un sistema guiado por excepciones, ya sea que
se presente un tipo de excepción o no. Veamos un ejemplo con unos interruptores
eléctricos en el listado 9.17.

Código 9.17 Otro ejemplo con la cláusula finally Switch

10 c l a s s S w i t c h {
20 boolean s t a t e = f a l s e ;
30 boolean r e a d ( ) {
40 return s t a t e ;
50 }
60 v o i d on ( ) {
70 s t a t e = true ;
80 }
90 void o f f ( ) {
100 state = false ;
110 }
120 }

Código 9.18 Un ejemplo más con la cláusula finally OnOffException1

10 c l a s s O n O f f E x c e p t i o n 1 extends E x c e p t i o n {
20 public OnOffException1 () {
30 super ( ) ;
40 }
50 p u b l i c O n O f f E x c e p t i o n 1 ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }
9.5 El enunciado finally 454

Código 9.19 Más ejemplos con la cláusula finally OnOffException2

10 c l a s s O n O f f E x c e p t i o n 2 extends E x c e p t i o n {
20 public OnOffException2 () {
30 super ( ) ;
40 }
50 p u b l i c O n O f f E x c e p t i o n 2 ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }

Código 9.20 Ejemplo adicional con la cláusula finally OnOffSwitch

10 c l a s s O n O f f S w i t c h {
20 s t a t i c S w i t c h sw = new S w i t c h ( ) ;
30 s t a t i c void f ( )
40 throws O n O f f E x c e p t i o n 1 , O n O f f E x c e p t i o n 2 {
50 }
60 }

Código 9.21 Ejemplo final con la cláusula finally ConFinally

10 p u b l i c c l a s s C o n F i n a l l y {
20 s t a t i c S w i t c h sw = new S w i t c h ( ) ;
30 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
40 try {
50 sw . on ( ) ;
60 // C ó d i g o que puede l a n z a r e x c e p c i o n e s
70 OnOffSwitch . f ( ) ;
80 }
90 catch ( O n O f f E x c e p t i o n 1 e ) {
100 System . e r r . p r i n t l n ( " OnOffException1 " ) ;
110 }
120 catch ( O n O f f E x c e p t i o n 2 e ) {
130 System . e r r . p r i n t l n ( " OnOffException2 " ) ;
140 }
150 finally {
160 sw . o f f ( ) ;
170 }
180 }
190 }

En esta aplicación deseamos que, ya sea que se haya podido o no prender el


interruptor, la aplicación lo apague antes de salir.
Los bloques try se pueden anidar para colocar de mejor manera las cláusulas
finally, obligando a ejecutar de adentro hacia afuera. En el listado 9.22 en la página
opuesta tenemos un ejemplo de anidamiento de bloques try.
455 Manejo de errores en ejecución

Código 9.22 Excepción opara mostrar anidamiento de bloques try CuatroException

10 c l a s s C u a t r o E x c e p t i o n extends E x c e p t i o n {
20 public CuatroException () {
30 super ( ) ;
40 }
50 p u b l i c C u a t r o E x c e p t i o n ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }

Código 9.23 Anidamiento de bloques try SiempreFinally

10 p u b l i c c l a s s S i e m p r e F i n a l l y {
20 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
30 System . o u t . p r i n t l n ( " Entrando al primer bloque try" ) ;
40 try {
50 System . o u t . p r i n t l n ( " Entrando al segundo bloque try" ) ;
60 try {
70 throw new C u a t r o E x c e p t i o n ( ) ;
80 }
90 finally {
100 System . o u t . p r i n t l n ( " Finally en el segundo "
110 + " bloque try" ) ;
120 }
130 }
140 catch ( C u a t r o E x c e p t i o n e ) {
150 System . e r r . p r i n t l n ( " Atrapando CuatroException en " +
160 "el primer bloque try" ) ;
170 }
180 finally {
190 System . e r r . p r i n t l n ( " Finally en primer bloque try" ) ;
200 }
210 }
220 }

Como en la mayorı́a de los casos, la cláusula finally se ejecuta de adentro hacia


afuera. No importa que el primer try no tenga manejador para la excepción, porque
al lanzarse la excepción y no encontrar un manejador en su entorno inmediato,
simplemente va a salir y utilizar el manejador del bloque try más externo. El
resultado de la ejecución se puede ver en la figura 9.12 en la siguiente página.
9.6 Restricciones para las excepciones 456

Figura 9.12 Ejecución de SiempreFinally


e l i s a @ l a m b d a . . . ICC1 / p r o g s / e x c e p c i o n e s % j a v a S i e m p r e F i n a l l y
Entrando a l primer bloque try
Entrando a l segundo bloque try
F i n a l l y en e l s e g u n d o b l o q u e t r y
A t r a p a n d o C u a t r o E x c e p t i o n en e l p r i m e r b l o q u e t r y
F i n a l l y en p r i m e r b l o q u e t r y

9.6 Restricciones para las excepciones

Cuando se redefine el método de una clase, el método redefinido no puede


lanzar más excepciones (o distintas) que el método original. Esto es para que si
alguien usa herencia para manejar ciertos objetos, no resulte que el método en la
superclase ya no funciona porque el método en la subclase lanza más excepciones
que el original. Esto es, si un método en la superclase no lanza excepciones, ese
método redefinido en las subclases tampoco puede lanzar excepciones.
Lo que sı́ puede hacer un método redefinido es lanzar excepciones que resultan
de extender a las excepciones que lanza el método de la superclase. En este caso
no hay problema.

9.6.1. Apareamiento de excepciones

En general, el manejador de una excepción se va a ejecutar en cualquiera de


las situaciones siguientes:
La clase a la que pertenece la excepción aparece en una cláusula catch que
corresponde al bloque try en el que se lanzó la excepción.
Alguna de las superclases de la excepción lanzada aparece en una cláusula
catch que corresponde al bloque try en el que se lanzó la excepción.
Cualquiera de estas dos situaciones que se presente, se ejecutará el manejador
que aparezca primero. Si en la lista de manejadores aparecen tanto la superclase
como la clase, y la superclase aparece primero, el compilador dará un mensaje de
error de que el segundo manejador nunca puede ser alcanzado.
457 Manejo de errores en ejecución

9.7 Recomendaciones generales

Las excepciones en general se usan en cualquiera de las siguientes circunstancias:

i. Arreglar el problema y llamar otra vez al método que causó la excepción.


ii. Parchar el proceso y continuar sin volver a intentar el método.
iii. Calcular algún resultado alternativo en lugar del que el método se supone que
debı́a haber calculado.
iv. Hacer lo que se pueda en el contexto actual y relanzar la excepción para que
sea manejada en un contexto superior.
v. Hacer lo que se pueda en el contexto actual y lanzar una excepción distinta
para que sea manejada en un contexto superior.
vi. Terminar el programa.
vii. Simplificar el algoritmo.
viii. Hacer una aplicación (o biblioteca) más segura (se refleja a corto plazo en la
depuración y a largo plazo en la robustez de la aplicación).

Con esto damos por terminado este tema, aunque lo usaremos extensivamente
en los capı́tulos que siguen.

Ejercicios

9.1.- ¿Qué está mal en el código siguiente? Identifica todos los errores de sintaxis.
10 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
20 S c a n n e r e s c a n e r = new S c a n n e r ( System . i n ) ;
30 try {
40 i n t num = e s c a n e r . n e x t I n t ( ) ;
50 i f (num > 1 0 0 ) {
60 catch new E x c e p t i o n ( " Fuera del lı́mite " ) ;
70 }
80 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Entrada inválida" ) ;
100 } f i n a l l y ( Exception e ) {
110 System . o u t . p r i n t l n ( " Hecho " ) ;
120 }
130 }
9. Ejercicios 458

9.2.- Determina la salida del pedazo de código siguiente cuando se teclean, suce-
sivamente, las siguientes entradas: (a) 12 (b) -12 (c) a12
10 try {
20 i n t num = e s c a n e r . n e x t I n t ( ) ;
30 i f (num < 0 ) {
40 throw new E x c e p t i o n ( " Negativo no" ) ;
50 }
60 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
70 System . o u t . p r i n t l n ( " Entrada inválida " ) ;
80 } catch ( E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Error : " + e . g e t M e s s a g e ( ) ) ;
100 } finally {
110 System . o u t . p r i n t l n ( " Terminado " ) ;
120 }

9.3.- Determina la salida del pedazo de código anterior si se ponen las lı́neas 80
y 90 antes que las lı́neas 60 y 70, como se muestra a continuación y si se le
alimenta, sucesivamente, las siguientes entradas: (a) 12 (b) -12 (c) a12
10 try {
20 i n t num = e s c a n e r . n e x t I n t ( ) ;
30 i f (num < 0 ) {
40 throw new E x c e p t i o n ( " Negativo no" ) ;
50 }
60 } catch ( E x c e p t i o n e ) {
70 System . o u t . p r i n t l n ( " Error : " + e . g e t M e s s a g e ( ) ) ;
80 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Entrada inválida " ) ;
100 } finally {
110 System . o u t . p r i n t l n ( " Terminado " ) ;
120 }

En algunos compiladores este pedazo de código da error de sintaxis al llegar


a la lı́nea 80, con el mensaje de que esa excepción ya está siendo manejada.
¿Por qué es el error?

9.4.- El siguiente segmento de código reporta varios errores de sintaxis. Dı́ cuáles
son, explica por qué se presentan y dı́ cómo arreglarlos sin eliminar las
excepciones.
459 Manejo de errores en ejecución

10 p u b l i c s t a t i c S t r i n g e j e r c i c i o 4 ( O b j e c t l i s t a ) {
20 i f ( l i s t a == n u l l ) {
30 throw new E x c e p t i o n ( " lista vacı́a " ) ;
40 }
50 return l i s t a . t o S t r i n g ( ) ;
60 }
70
80 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
90 O b j e c t nuevo = n u l l ;
100 System . o u t . p r i n t l n ( " nuevo es: " + e j e r c i c i o 4 ( nuevo ) ) ;
110 }

9.5.- Si el código del ejercicio anterior, en la lı́nea 30, en lugar de lanzar la excep-
ción Exception lanza la excepción NullPointerException, el programa compila
y ejecuta bien. Explica las razones.
9.6.- Tenemos el siguiente método recursivo para calcular el factorial de un entero
positivo:
10 p u b l i c long f a c t o r i a l ( i n t n ) {
20 i f ( n == 1 ) r e t u r n 1 ;
30 r e t u r n n ∗ f a c t o r i a l ( n  1);
40 }

Si a este método se le pasa como argumento un entero negativo, el méto-


do recurrirá para siempre. Define una excepción vigilada que haga que la
función reaccione frente a esta situación mandando un mensaje indicando
lo que pasó.
9.7.- Agrega una lı́nea al principio del método para que si el entero es negativo
lance la excepción que definiste al método que la llamó. Esta excepción debe
ser manejada en todos los puntos en donde hay invocaciones a factorial.
Modifica el método para que ası́ sea y dı́ cómo debe ser llamada desde fuera.
9.8.- Modifica el método anterior para la excepción sea atrapada y manejada
dentro del método, regresando un valor inválido.
9.9.- Tenemos un método que busca en una lista ligada la presencia de una ca-
dena. Codifica el método para objetos que tengan un campo de información
info y una referencia al siguiente sigue. Puedes suponer los métodos get y
set correspondientes. El método recibe como parámetro una cadena y debe
regresar la referencia del objeto que tenga a esa cadena como subcadena.
Agrega excepciones para que el método lance excepciones si:
9. Ejercicios 460

(a) La cadena que le pasan es nula o vacı́a.


(b) La cadena no se encuentra en ningún elemento de la lista.

9.10.- Cuando declaras tus propias excepciones, ¿deben ser vigiladas o no? Justi-
fica.
Entrada y salida
10
10.1 Conceptos generales

Uno de los problemas que hemos tenido hasta el momento es que las bases
de datos que hemos estado construyendo no tienen persistencia, esto es, una vez
que se descarga la aplicación de la máquina virtual (que termina la aplicación) la
información que generamos no vive más allá –excepto, posiblemente, si la salida
de la aplicación se redirigió a disco–. No tenemos manera de almacenar lo que
construimos en una sesión para que, en la siguiente sesión, empecemos a partir de
donde nos quedamos. Prácticamente en cualquier aplicación que programemos y
usemos vamos a requerir de mecanismos que proporcionen persistencia a nuestra
información.
En los lenguajes de programación, y en particular en Java, esto se logra median-
te archivos, que son conjuntos de datos guardados en un medio de almacenamiento
externo. Los archivos sirven de puente entre la aplicación y el medio exterior, ya
sea para comunicarse con el usuario o para, como acabamos de mencionar, darle
persistencia a nuestras aplicaciones.
Hasta ahora hemos usado extensamente la clase Scanner, que nos permite apli-
car mecanismos de revisión de cadenas a la consola (o a una cadena). También
hemos usado dos archivos (objetos) que están dados en Java y que son System.out
y System.err. Ambos objetos corresponden a archivos de salida; el primero es para
10.1 Conceptos generales 462

salida normal en consola y el segundo para salida, también en consola, pero de


reporte de errores, como los que suceden en una aplicación y que generan una ex-
cepción. Por ejemplo, cuando un programa que aborta reporta dónde se lanzó la
excepción, el reporte lo hace a System.err –noten que aun cuando redirijamos la
salida desde el sistema operativo a un archivo en disco llamado “salida.txt” por
ejemplo, cuando invocamos la ejecución de una clase con
java miClase > salida.txt
los mensajes de error siguen apareciendo en la consola–.
La razón por la que usamos un objeto de la clase Scanner hasta el momento es
que en Java prácticamente toda la entrada y salida –sobre todo la entrada– puede
lanzar excepciones; eso implica que cada vez que usemos un archivo para leer,
escribir, crearlo, eliminarlo y, en general, cualquier operación que tenga que ver
con archivos, esta operación tiene que ser vigilada en un bloque try, con el manejo
correspondiente de las excepciones que se pudieran lanzar. Como Scanner no es
un objeto de entrada y salida y la entrada a través de System.in ya está creada e
inicializada al empezar nuestra aplicación, podemos aplicar un objeto de la clase
Scanner, al construirlo con new, ya sea a una cadena o a un archivo de entrada que
ya esté inicializado. El significado de esto es, simplemente, que vamos a ver al flujo
de entrada o a la cadena como a través de cristales de colores que nos permitan
separar elementos de la cadena o flujo –como interpretar un entero, buscar un
carácter de fin de lı́nea, agrupar en una cadena, etc.–.
El diseñar los métodos de entrada y salida para que lancen excepciones en
caso de error es no sólo conveniente sino necesario, pues es en la interacción con
un usuario cuando la aplicación puede verse en una situación no prevista, como
datos erróneos, un archivo que no existe o falta de espacio en disco para crear un
archivo nuevo.
Un concepto muy importante en la entrada y salida de Java es el de flujos de
datos. Java maneja su entrada y salida como flujos de caracteres (ya sea de 8 o
16 bits). En el caso de los flujos de entrada, éstos proporcionan caracteres, uno
detrás de otro en forma secuencial, para que el programa los vaya consumiendo
y procesando. Los flujos de salida funcionan de manera similar, excepto que es
el programa el que proporciona los caracteres para que sean proporcionados al
mundo exterior, también de manera secuencial.
En las figuras 10.1 y 10.2 en la página opuesta vemos los algoritmos generales
para lectura y escritura, no nada más para Java, sino para cualquier lenguaje
de programación. En el caso de querer un flujo de entrada, el flujo tiene que
localizarse y ponerse listo para leer de él. Si el flujo no existe (por ejemplo, en
disco), no puede seguir adelante la aplicación. En el caso de los flujos de salida
se tiene que preparar el espacio donde se va a escribir o podrı́a darse el caso de
463 Entrada y salida

que se quisiera extender un archivo que ya existe (append ). En este último caso
también deberá verificar que el archivo ya exista y que esté disponible. Tanto en
flujos de entrada como de salida el sistema deberá verificar que la aplicación tenga
los permisos necesarios para acceder, crear y/o escribir en el flujo especificado.

Figura 10.1 Algoritmo para el uso de flujos de entrada


$ !
'
' Inicializar Abrir el flujo
'
'
'
' $
'
'
'
& '
&
Lectura de Procesar información
(mientras haya) Leer información
caracteres '
' '
%
'
'
'
'
'
' !
'
%Final Cerrar el flujo

Figura 10.2 Algoritmo para el uso de flujos de salida


$ !
'
' Inicializar Abrir el flujo
'
'
'
' $
'
'
'
& '
&
Escritura de Procesar información
caracteres ' (mientras haya) Escribir información
' '
%
'
'
'
'
'
' !
'
%Final Cerrar el flujo

Las actividades más tardadas que lleva a cabo una aplicación son las de entra-
da y salida, pues tienen que comunicarse con el mundo exterior de los dispositivos
fı́sicos. En general, el sistema se tiene que “sentar” a esperar que fluya la infor-
mación; para acortar estos tiempos el sistema lee (o escribe) por bloques: lee un
bloque completo y va proporcionando de ahı́ dato por dato; cuando el bloque de
memoria se vacı́a, carga un nuevo bloque; o escribe a un bloque de memoria (lla-
mado buffer ) y cuando el bloque se llena es cuando lo escribe al dispositivo fı́sico.
Decimos entonces que el proceso de entrada/salida está “gestionado” a través de
un buffer.
Los flujos de entrada y salida, como su nombre lo indica, fluyen. En un flujo de
entrada la aplicación va consumiendo el flujo como se le va presentando, por lo que
al abrir el flujo deberemos tener un apuntador o referencia a cuál es el siguiente
10.2 Jerarquı́a de clases 464

elemento disponible del flujo, que deberı́a ser el primer elemento del mismo; en
el caso de un flujo de salida deberemos saber hasta dónde se ha emitido el flujo
y que al abrirse deberá decir que no se ha emitido nada. Con cada operación de
entrada/salida esta referencia se debe actualizar para que el objeto sea capaz de
ir entregando (produciendo) los caracteres conforme se solicitan (emiten).
Podemos ver un esquema de este funcionamiento en las figuras 10.3 y 10.4,
donde la aplicación los produce y consume en el orden en que se muestran en el
flujo.

Figura 10.3 Funcionamiento de flujo de entrada

Dispositivo D
A

T Leer
O S Aplicación

Figura 10.4 Funcionamiento de flujo de salida

Escribir
Aplicación D
A

T
O S Dispositivo

Los flujos de entrada se manejan a través de clases especı́ficas para ellos. Al


construir el objeto se abre el flujo; se lee de él o escribe en él utilizando los distintos
métodos que tenga la clase para ello; se cierra invocando al método close() del
objeto correspondiente.
En lo que sigue elaboraremos métodos para hacer persistente nuestra base de
datos. Antes trataremos de tener una visión más general de cómo maneja Java la
entrada y salida.
465 Entrada y salida

10.2 Jerarquı́a de clases

La entrada y salida se maneja en Java a través de una jerarquı́a que incluye


clases e interfaces. Tenemos básicamente dos maneras de hacer entrada y salida:
la primera es leyendo y escribiendo bytes, mientras que la segunda es leyendo y
escribiendo caracteres Unicode. Dado que Java es fundamentalmente un lenguaje
cuya caracterı́stica principal es su portabilidad, se diseñó un juego de caracteres
universales, de dos bytes cada uno, que cubre prácticamente todos los alfabe-
tos conocidos. Para asegurar la portabilidad de datos, y dado que Java maneja
internamente Unicode, es que se diseñaron estas clases que manejan caracteres.
Cada uno de los tipos de entrada y salida tiene una superclase abstracta para
lectura y otra para escritura. De ella se derivan clases concretas que permiten ma-
nipular de alguna forma lo que se está leyendo o escribiendo. Iremos describiendo
su uso conforme las vayamos presentando.
La entrada y salida se ve siempre como un flujo, ya sea de bytes o de caracteres.
Se va tomando unidad por unidad y se procesa. Cuando este flujo se termina
decimos que se acabó el archivo y tendremos un eof (fin de archivo). Generalmente
procesaremos la información hasta encontrar un eof, en cuyo momento daremos
fin al proceso de los datos. Es por esta caracterı́stica que Java llama a su entrada
y salida un stream. Hablaremos de un flujo de bytes o de un flujo de caracteres
(omitiendo Unicode).

10.3 Entrada y salida de bytes


Un byte es un entero que ocupa 8 bits, y en general se da como unidad de
medida para otros tipos que ocupan más espacio. Los enteros que podemos alma-
cenar en un byte van del -128 al 127. Sin embargo, cuando pensamos en código
ASCII, pensamos en caracteres cuyo valor está entre 0 y 255. Para que podamos
manejar ası́ los bytes, la lectura (y la escritura) se hará siempre en enteros o en
caracteres Unicode, de tal manera que el método utilice únicamente el byte más
bajo (al que corresponden las posiciones más de la derecha).
En la figura 10.5 en la siguiente página se encuentra la jerarquı́a de clases para
InputStream, mientras que en la figura 10.6 en la página 469 está el esquema de la
jerarquı́a de clases para OutputStream.
10.3 Entrada y salida de bytes 466

Figura 10.5 Jerarquı́a de clases para InputStream.

DataInput

InputStream ObjectInput

ObjectInputStream

AudioInputStream
LineNumberInputStream

SequenceInputStream
DataInputStream

ByteArrayInputStream
BufferedInputStream

FilterInputStream
PushBackInputStreeam

FileInputStream
CheckedInputStream

PipedInputStream
CipherInputStream

SequenceInputStream
DigestInputStream

StringBufferInputStream
InflaterInputStream

ProgressMonitorInputStream

A continuación damos una muy breve explicación en orden alfabético de cuál es


el uso de cada una de las subclases para entrada. Todas las subclases se encuentran
en el paquete java.io, excepto cuando indiquemos explı́citamente que no es ası́.
467 Entrada y salida

public class BufferedInputStream extends FilterInputStream


Lee desde un InputStream guardando lo que va leyendo en un buffer. Esto
permite a la aplicación marcar una posición o regresar a bytes ya leı́dos.
public class ByteArrayInputStream extends InputStream
Contiene un buffer interno que contiene bytes, que se leen del flujo, cuando
es necesario.
public class CheckedInputStream extends FilterInputStream
(Paquete: java.util.zip)
Es un InputStream que mantiene una suma de verificación (checksum) de los
datos que ha leı́do.
public class CipherInputStream extends FilterInputStream
(Paquete: javax.crypto)
Está compuesto de un InputStream y un Cipher que permite entregar cifrados
los bytes que lee de la entrada.
public class DataInputStream extends FilterInputStream
implements DataInput
Lee datos primitivos (enteros, reales, booleanos, etc.) de un InputStream
subyacente de manera independiente de la máquina.
public class FileInputStream extends InputStream
El flujo de entrada reside en un archivo en disco.
public class FilterInputStream extends InputStream
Simplemente recibe el flujo de un flujo subyacente y los pasa a la aplicación.
Redefine los métodos de entrada para poder “transformarla”.
public class DigestInputStream extends FilterInputStream
(paquete: java.security)
Actualiza el mensaje digerido (MesasageDigest) usando para ello los bytes
que pasan por el flujo.
public class InflaterInputStream extends FilterInputStream
(Paquete: java.util.zip)
Implementa un filtro para descomprimir datos comprimidos con deflate y
para otros filtros de descompresión.
public abstract class InputStream implements Closeable
Es la superclase de todas las clases que manejan flujos de entrada de bytes.
10.3 Entrada y salida de bytes 468

public class LineNumberInputStream extends FilterInputStream


(sobreseido1 )
Este flujo lleva la cuenta del número de lı́neas que ha leı́do. Una lı́nea es una
sucesión de bytes que terminan con zr, zn o un retorno de carro seguido de
una alimentación de lı́nea. Al entregar las lı́neas leı́das convierte cualquiera
de los terminadores a zn.
class ObjectInputStream extends InputStream
implements ObjectInput, ObjectStreamConstants
Escribe datos primitivos y gráficas de objetos de Java en un flujo de by-
tes. Únicamente objetos que implementen la interfaz Serializable pueden ser
escritos en este flujo.
public class PipedInputStream extends InputStream
Se usa con hilos paralelos de ejecución, donde uno de los hilos usa un Piped-
InputStream para adquirir datos y el otro usa un PipedOutputStream para
entregar datos. Ambos hilos pueden hacer un proceso de la información del
flujo correspondiente.
public class ProgressMonitorInputStream extends FilterInputStream
(Paquete: javax.swing)
Vigila el progreso al leer de un InputStream, presentando, en su caso, venta-
nas de diálogo.
public class PushBackInputStream extends FilterInputStream
Trabaja sobre un InputStream subyacente y permite “desleer” un byte. Este
proceso es útil cuando estamos buscando, por ejemplo, un byte que tiene
dos funciones: delimitar lo que está antes y empezar lo nuevo. En este caso
es conveniente desleerlo para el segundo papel que juega.
public class SequenceInputStream extends InputStream
Es capaz de concatenar, para lectura, a varios flujos de entrada como si
fueran uno solo.
public class StringBufferInputStream extends InputStream
Permite crear una aplicación en la que el flujo proviene de una cadena de
caracteres, en lugar de venir de un dispositivo.
La jerarquı́a de clases para los flujos de salida de bytes se da en la figura 10.6.
Con sus marcadas excepciones por el uso que se le pueda dar, hay una correspon-
dencia entre ambas jerarquı́as.

1
Corresponde a deprecated, que indica que no se recomienda su uso porque ya no va a ser
actualizada y soportada
469 Entrada y salida

Figura 10.6 Jerarquı́a de clases para OutputStream.

DataOutput

OutputStream ObjectOutput

ObjectOutputStream

ByteArrayOutputStream

PipedOutputStream

FileOutputStream

FilterOutputStream

PrintStream

BufferedOutputStream

DataOutputStream

La única clase que no tiene contra parte en la jerarquı́a de entrada de bytes es


PrintStream:

public class PrintStream extends FilterOutputStream


implements Appendable
Agrega funcionalidad a otro OutputStream aportando la habilidad de im-
primir de manera conveniente diversos valores de datos. Adicionalmente,
sus métodos nunca lanzan excepciones y puede construirse de tal manera
que evacúe automáticamente.
10.4 Entrada y salida de caracteres 470

10.4 Entrada y salida de caracteres

Cuando hablamos de caracteres en el contexto de Java nos referimos a carac-


teres Unicode, donde cada uno ocupa 2 bytes (16 bits). Tenemos una jerarquı́a
similar a la que maneja bytes para caracteres Unicode, las superclases Writer y
Reader, cuyas jerarquı́as se muestra en las figuras 10.7 y 10.8 respectivamente. En
todos los casos las subclases sombreadas se refieren a clases que van a hacer un
proceso intermedio de los datos entre el origen y el destino de los mismos. Un
esquema de qué significa esto se encuentra en la figura 10.14. En ésta el origen de
los datos puede ser la aplicación y el destino el dispositivo, en el caso de que se
esté efectuando escritura; o bien el origen es el dispositivo que entrega los datos
“en crudo” y la aplicación la que los recibe en el destino ya procesados.

Figura 10.7 Jerarquı́a de clases para Writer.

BufferedWriter

CharArrayWriter

FilterWriter

Writer PrintWriter

PipedWriter

StringWriter

OutputStreamWriter FileWriter
471 Entrada y salida

Figura 10.8 Jerarquı́a de clases para Reader.

StringReader

CharArrayReader

PipedReader
Reader
BufferedReader LineNumberReader

FilterReader PushbackReader

InputStreamReader FileReader

Figura 10.9 Entrada/Salida con proceso intermedio (filtros)

Filtro

Origen Destino

También estas jerarquı́as corren paralelas a las que trabajan con bytes, por lo
que no daremos una nueva explicación de cada una de ellas. Se aplica la misma
explicación, excepto que donde dice “byte” hay que sustituir por “carácter”. Úni-
10.4 Entrada y salida de caracteres 472

camente explicaremos aquellas clases que no tienen contra parte en bytes.

public class StringWriter extends Writer


Escribe su salida en un buffer de tipo String, que puede ser utilizado a su
vez para construir una cadena.
public class OutputStreamWriter extends Writer
Funciona como un puente entre flujos de caracteres y flujos de bytes,
codificados de acuerdo a un conjunto de caracteres especı́fico.

Vale la pena hacer la aclaración que en este caso los flujos que leen de y escriben
a archivos en disco extienden a las clases InputStreamReader y OutputStreamWriter
respectivamente, ya que la unidad de trabajo en los archivos es el byte (8 bits) y
no el carácter (16 bits). Por lo demás funcionan igual que sus contra partes en los
flujos de bytes.
Es conveniente mencionar que las versiones actuales de Java indican que las
clases que se deben usar son las que derivan de Reader y Writer y no las que son
subclases de InputStream y OutputStream. Ambas jerarquı́as (las de bytes y las
de caracteres) definen prácticamente los mismos métodos para bytes y caracteres,
pero para fomentar la portabilidad de las aplicaciones se ha optado por soportar
de mejor manera las clases relativas a caracteres.
Sin embargo, como ya mencionamos, la entrada y salida estándar de Java es
a través de clases que pertenecen a la jerarquı́a de bytes (System.in, System.out y
System.err).
Lo primero que queremos poder hacer es leer desde el teclado y escribir a
pantalla. Esto lo necesitamos para la clase que maneja el menú y de esta manera
ir abriendo las cajas negras que nos proporcionaba la clase Consola para este fin.

10.4.1. Entrada y salida estándar

La entrada y salida desde teclado y hacia consola se hace a través de la clase


System. Esta clase ofrece, además de los objetos para este tipo de entrada y salida,
muchı́simos métodos que van a ser útiles en aplicaciones en general. La clase
System tiene tres atributos que son:
public static f i n a l PrintStream e r r ;
public s t a t i c f i n a l InputStream in ;
public s t a t i c f i n a l PrintStream out ;
473 Entrada y salida

Por ser éstos tres objetos estáticos de la clase System se pueden usar sin cons-
truir objetos. Todo programa en ejecución cuenta con ellos, por lo que los puede
usar, simplemente refiriéndose a ellos a través de la clase System.
El primero de ellos es un archivo al que dirigiremos los mensajes que se refieran
a errores, y que no queramos “mezclar” con la salida normal. El segundo objeto es
para leer de teclado (con eco en la pantalla) y el tercero para escribir en la pantalla.
Las dos clases mencionadas son clases concretas que aparecen en la jerarquı́a de
clases que mostramos en las figuras 10.5 en la página 466 y 10.6 en la página 469.
Si bien la clase PrintStream se va a comportar exactamente igual a Consola,
en cuanto a que “interpreta” enteros, cadenas, flotantes, etc. para mostrarlos con
formato adecuado, esto no sucede con la clase InputStream que opera de manera
muy primitiva, leyendo byte por byte, y dejándole al usuario la tarea de pegar los
bytes para interpretarlos. Más adelante revisaremos con cuidado todos los méto-
dos de esta clase. Por el momento únicamente revisaremos los métodos que leen
byte por byte, y que son:

public class InputStream implements Closeable


Constructores:
public InputStream()
Constructor por omisión
Métodos:
public int read() throws IOException
Lee el siguiente byte del flujo de entrada. Devuelve un valor entre 0 y 255.
Si se acaba el archivo (o desde el teclado se oprime Ctrl-D) regresa -1.
public int read(byte[] b) throws IOException
Lee un número de bytes al arreglo. Regresa el número de bytes leı́do.
Bloquea la entrada hasta tener datos de entrada disponibles, se encuentre
el fin de archivo o se lance una excepción. Se leen, a lo más, el número de
bytes dado por el tamaño de b.
public int read(byte[] b, int off , int len ) throws IOException
Lee a lo más len bytes de datos desde el flujo de entrada y los acomoda en el
arreglo de bytes b. Regresa el número de bytes leı́dos. El primer byte leı́do
se acomoda en b[off]. Lanza una excepción IndexOutOfBoundsException si
off es negativo, len es negativo o off len ¡ b.length.
10.5 El manejo del menú de la aplicación 474

Como podemos ver de los métodos de la clase InputStream, son muy primitivos
y difı́ciles de usar. Por ello, como primer paso en la inclusión de entrada y salida
completa en nuestra aplicación, para entrada utilizaremos una subclase de Reader,
BufferedReader, más actual y mejor soportada.

10.5 El manejo del menú de la aplicación

En el caso de los flujos System.out y System.err no tenemos que hacer abso-


lutamente nada pues existen como atributos estáticos de la clase System, por lo
que los podemos usar directamente. Conviene, sin embargo, listar los métodos y
atributos de la clase PrintStream, que es una subclase de FilterOutputStream, que
es, a su vez, una subclase de OutputStream.

10.5.1. La clase OutputStream

Esta es una clase abstracta que deja sin implementar uno de sus métodos. El
constructor y los métodos se listan a continuación:

public class OutputStream implements Closeable, Flushable


Constructores:
public OutputStream()
Constructor por omisión.
Métodos:
public abstract void write(int b) throws IOException
Toma el entero b y escribe únicamente los 8 bits más bajos, descartando
los otros 24 bits. El programador de clases que hereden de ésta tiene que
definir este método.
public void write (byte[] b)
Escribe el contenido de los b.length bytes del arreglo b al flujo de salida.
475 Entrada y salida

public void write (byte[] b, int off , int len ) throws IOException
Escribe en el flujo de salida los bytes desde b[off] hasta b[off+len-1]. Si
hay un error en el ı́ndice o si b es null, lanza la excepción correspondiente
(como son ArithmeticException ambas no hay que vigilarlas). Si hay algún
error de I/O se lanza la excepción correspondiente.
public void flush () throws IOException
Evacúa el flujo, obligando a que los bytes que estén todavı́a en el buffer
sean escritos al medio fı́sico.
public void close () throws IOException
Cierra el flujo y libera los recursos del sistema asociados al flujo. Una vez
cerrado el flujo, cualquier otro intento de escribir en él va a causar una
excepción. Lanza una excepción (IOException) si se intenta reabrir. En
realidad no hace nada, sino que se tiene que reprogramar para que haga
lo que tiene que hacer.

Esta es la superclase de la clase que estamos buscando. De manera intermedia


hereda a la clase FilterOutputStream, que procesa el flujo antes de colocarlo en
el dispositivo de salida. Además de los métodos heredados de OutputStream y de
implementar al método que lee de un entero, agrega los métodos que l;istamos a
continuación. Tiene la siguiente definición:

public class FilterOutputStream extends OutputStream


Campo:
protected OutputStream out
El flujo de salida subyacente a ser filtrado.
Constructor:
public FilterOutputStream(OutputStream out)
Crea un filtro para el flujo out.
Método:
public void write (int b) throws IOException
Implementa el método abstracto write(int) de su superclase.
10.5 El manejo del menú de la aplicación 476

El resto de los métodos que hereda de OutputStream simplemente los redefine


a que invoquen al método correspondiente de su superclase, por lo que no los
listamos nuevamente. Sin embargo, como mencionamos antes, tanto out como err
se construyen como objetos de la clase PrintStream, que presenta varios métodos,
además de los que hereda de FilterOutputStream (hereda, entre otros, el campo que
corresponde al flujo de salida FilterOutputStream out). Listaremos sólo algunos de
estos métodos. La lista exhaustiva se puede ver en la documentación de Java.

public class PrintStream extends FilterOutputStream


implements Appendable
Constructores:
public PrintStream(OutputStream out)
Construye un PrintStream que no auto-evacúa.
public PrintStream(OutputStream out, boolean autoFlush)
Construye un nuevo PrintStream. Si autoFlush es verdadero el buffer va
a evacuar cuando se escriba un arreglo de bytes, se invoque un método
println o se escriba un carácter de lı́nea nueva (’zn’).
public PrintStream(String fileName) throws FileNotFoundException
Busca escribir en un archivo en disco con nombre fileName. Crea el flujo
intermedio OutputStreamWriter necesario.
Métodos:
public void close ()
Cierra el flujo, evacuando y cerrando el flujo de salida subyacente.
public boolean checkError()
Evacúa el flujo y verifica su estado de error. Este es verdadero si el flujo
de salida subyacente lanza una IOException distinta de InterruptedIOEx-
ception, y cuando se invoca al método setError.
protected void setError()
Establece en verdadero el estado de error del flujo.
public void write (int b)
Sobreescribe el método de OutputStream escribiendo el byte más bajo al
dispositivo.
477 Entrada y salida

public void write (byte[] buf, int off , int len )


Hace lo mismo que OutputStream.
public void print (boolean b)
Escribe un valor booleano. Escribe en bytes el valor dado por
String.valueOf(boolean).
public void print (char c)
Escribe un carácter, que se traduce a uno o más bytes, dependiendo de la
plataforma.
public void print (int i )
Escribe un entero, el valor dado por String.valueOf(int).
public void print ( xtipoy xidentify )
El xtipoy puede ser long, float, double y se escribe lo producido por
String.valueOf(xtipoy).
public void print (char[] s)
Escribe un arreglo de caracteres, convirtiéndolos a bytes.
public void print ( String s)
Escribe una cadena, tomando carácter por carácter y convirtiéndolo a
byte.
public void print (Object obj)
Usa el método String.valueOf(Object) para escribir, en bytes, lo solicitado.
public void println ()
Imprime únicamente un carácter de fin de lı́nea
public void println ( xtipoy xidentify )
Admite los mismos tipos de argumentos que print; al terminar de escribir
el argumento, escribe un carácter de fin de lı́nea.
public PrintStream printf ( String format, Object. . . args)
Un método para escribir una lista de argumentos con un formato dado
por format.
public PrintStream format( String format, Object ... args)
Método equivalente a printf de esta misma clase.
Nota: esta clase presenta muchı́simos métodos más que no veremos por el momento.
Para conocerlos consultar la documentación de las clases de Java.
10.6 El catálogo de carreras 478

10.6 El catálogo de carreras

Regresemos al catálogo de carreras que vimos únicamente en cuanto a su uso; la


razón expuesta para ello es que si querı́amos tener el catálogo en disco tenı́amos
que usar excepciones para manejarlo. Como ya tenemos ese conocimiento y la
especificación general de archivos, podemos proceder a abrir esa clase para ver
cómo está implementada. El código se encuentra en el listado 10.1.

Código 10.1 Implementación del catálogo de carreras CatalogoCarreras (1/4)

1 package u t i l e s ;
2 import j a v a . i o . ∗ ;
3 import j a v a . u t i l . S c a n n e r ;
4 /∗ ∗
5 ∗ C l a s s <code>C a t a l o g o C a r r e r a s </code> c a r g a a memoria e l c a t a l o g o de
6 ∗ carreras .
7 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a . f c i e n c i a s . unam . mx”></a>
8 ∗ @version 1.0
9 ∗/
10 public class CatalogoCarreras {
11 /∗ ∗
12 ∗ V a l o r v a r i a b l e <code>c a r r e r a s </code> p a r a l a s c l a v e s de l a s
13 ∗ carreras .
14 ∗/
15 p r i v a t e s t a t i c S t r i n g c a r r e r a s = "" ;
16 /∗ ∗
17 ∗ V a l o r v a r i a b l e <code>s C a r r e r a s </code> p a r a l o s nombres de l a s
18 ∗ carreras .
19 ∗/
20 p r i v a t e s t a t i c S t r i n g s C a r r e r a s = "" ;
21 /∗ ∗
22 ∗ V a l o r v a r i a b l e <code>h a y C a r r e r a s </code> p a r a m a r c a r cuando ya
23 ∗ s e hayan c a r g a d o l a s c a r r e r a s a memoria .
24 ∗/
25 p r i v a t e s t a t i c boolean h a y C a r r e r a s = f a l s e ;
26 /∗ ∗
27 ∗ C o n s t a n t e <code>TAM CLAVE</code> p a r a d a r e l tamaño de l a s
28 ∗ c l a v e s de c a r r e r a .
29 ∗/
30 p u b l i c s t a t i c f i n a l i n t TAM CLAVE = 3 ;
31 /∗ ∗
32 ∗ C o n s t a n t e <code>TAM NOMBRE</code> p a r a d a r e l tamaño de l o s
33 ∗ nombres de c a r r e r a .
34 ∗/
35 p u b l i c s t a t i c f i n a l i n t TAM NOMBRE = 3 6 ;
479 Entrada y salida

Código 10.1 Implementación del catálogo de carreras CatalogoCarreras (2/4)

37 /∗ ∗
38 ∗ Metodo <code>l e e C a r r e r a s </code> l e e l a s c l a v e s y nombres de l a s
39 ∗ c a r r e r a s d e s d e un a r c h i v o en d i s c o .
40 ∗ @param a r c h i v o v a l o r de t i p o <code>S t r i n g </code> p a r a
41 ∗/
42 public s t a t i c void l e e C a r r e r a s ( S t r i n g a r c h i v o ){
43 Scanner cons = n u l l ;
44 try {
45 c o n s = new S c a n n e r ( new F i l e ( a r c h i v o ) ) ;
46 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
47 hayCarreras = false ;
48 System . e r r . p r i n t l n ( "No pude leer el catalogo de carreras " ) ;
49 System . e x i t ( 1 ) ;
50 }
51 hayCarreras = true ;
52 // Se pudo a b r i r e l a r c h i v o
53 i n t n u m Ca r r e r a = 0 ;
54 S t r i n g nombreCarrera = n u l l ;
55 w h i l e ( n u m Ca r r e r a != 1) {
56 n u m Ca r r e r a = c o n s . n e x t I n t ( ) ;
57 i f ( n u m Ca r r e r a != 1) {
58 nombreCarrera = cons . nextLine ( ) ;
59 c a r r e r a s += Cadenas
60 . rellenaCampo ( I n t e g e r . t o S t r i n g ( numCarrera ) ,
61 TAM CLAVE , ’0’ , ’i’ ) ;
62 s C a r r e r a s += Cadenas
63 . r e l l e n a C a m p o ( n o m b r e C a r r e r a ,TAM NOMBRE, ’ ’ , ’d’ ) ;
64 }
65 }
66 cons . c l o s e ( ) ;
67 }
68
69 /∗ ∗
70 ∗ Metodo <code>g e t C a r r e r a s </code> r e g r e s a una c a d e n a con l a s
71 ∗ claves .
72 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
73 ∗/
74 public static String getCarreras () {
75 return c a r r e r a s ;
76 }
77
78 /∗ ∗
79 ∗ Metodo <code>g e t S C a r r e r a s </code> r e g r e s a una c a d e n a con l o s
80 ∗ nombres .
81 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
82 ∗/
83 public static String getSCarreras () {
84 return s C a r r e r a s ;
85 }
10.6 El catálogo de carreras 480

Código 10.1 Implementación del catálogo de carreras CatalogoCarreras (3/4)

87 /∗ ∗
88 ∗ Metodo <code>b H a y C a r r e r a s </code> r e g r e s a s i ya e s t á n c a r g a d a s
89 ∗ las carreras .
90 ∗ @ r e t u r n v a l o r de t i p o <code>b o o l e a n </code >.
91 ∗/
92 p u b l i c s t a t i c boolean b H a y C a r r e r a s ( ) {
93 return hayCarreras ;
94 }
95
96 /∗ ∗
97 ∗ Metodo <code>d a C a r r e r a </code >, dada l a c l a v e de l a c a r r e r a
98 ∗ r e g r e s a e l nombre .
99 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >.
100 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
101 ∗/
102 public s t a t i c S t r i n g daCarrera ( i n t c u a l ){
103 i f (! hayCarreras ) {
104 l e e C a r r e r a s ( " CarrerasCiencias .txt" ) ;
105 hayCarreras = true ;
106 }
107 S t r i n g s C u a l = Cadenas . r e l l e n a C a m p o ( I n t e g e r . t o S t r i n g ( c u a l ) ,
108 TAM CLAVE , ’0’ , ’i’ ) ;
109 i n t donde = c a r r e r a s . i n d e x O f ( s C u a l ) ;
110 i f ( donde == 1 | | ( donde % TAM CLAVE) != 0 ) {
111 r e t u r n Cadenas
112 . r e l l e n a C a m p o ( " Código invalido " ,TAM NOMBRE, ’ ’ , ’d’ ) ;
113 } else {
114 i n t empza = ( donde /TAM CLAVE) ∗TAM NOMBRE;
115 r e t u r n s C a r r e r a s . s u b s t r i n g ( empza , empza+TAM NOMBRE ) ;
116 }
117 }
118
119 /∗ ∗
120 ∗ Metodo <code>m u e s t r a C a t a l o g o </code> m u e s t r a e l c a t á l o g o de l a s
121 ∗ carreras .
122 ∗/
123 public s t a t i c void muestraCatalogo ( ) {
124 i f (! hayCarreras ) {
125 l e e C a r r e r a s ( " CarrerasCiencias .txt" ) ;
126 hayCarreras = true ;
127 }
128
129 System . o u t . p r i n t l n ( " Catalogo de carreras de la Facultad "
130 + " de Ciencias \n"
131 +" ================================== "
132 + " ============= " ) ;
481 Entrada y salida

Código 10.1 Implementación del catálogo de carreras CatalogoCarreras (4/4)

133 f o r ( i n t i =0, j =0;


134 i< carreras . length ();
135 i+=TAM CLAVE , j+=TAM NOMBRE) {
136 System . o u t . p r i n t l n ( c a r r e r a s . s u b s t r i n g ( i , i+TAM CLAVE)+"\t"
137 + s C a r r e r a s . s u b s t r i n g ( j , j+TAM NOMBRE)
138 + "\n" ) ;
139 }
140 System . o u t . p r i n t l n ( " =========================================== "
141 + " ============== " ) ;
142 }
143
144 /∗ ∗
145 ∗ Metodo <code>e s C a r r e r a </code> d i c e s i l a c l a v e p r o p o r c i o n a d a e s
146 ∗ valida .
147 ∗ @param c u a l v a l o r de t i p o <code>i n t </code> c l a v e p r o p o r c i o n a d a .
148 ∗ @ r e t u r n v a l o r de t i p o <code>b o o l e a n </code >.
149 ∗/
150 p u b l i c s t a t i c boolean e s C a r r e r a ( i n t c u a l ) {
151 i f (! hayCarreras ) {
152 l e e C a r r e r a s ( " ConsultasListas / CarrerasCiencias .txt" ) ;
153 hayCarreras = true ;
154 }
155 S t r i n g s C u a l = Cadenas . r e l l e n a C a m p o ( I n t e g e r . t o S t r i n g ( c u a l ) ,
156 TAM CLAVE , ’0’ , ’i’ ) ;
157 i n t donde = c a r r e r a s . i n d e x O f ( s C u a l ) ;
158 i f ( donde == 1 | | ( donde % TAM CLAVE) != 0 ) {
159 return f a l s e ;
160 }
161 return true ;
162 }
163 }

Lo primero que vemos en la implementación de esta clase es que estamos


importando a las clases en el paquete java.io, además de Scanner, que ya lo im-
portábamos –lı́neas 2 y 3–. La clase consiste de métodos estáticos. Es necesario
importar todo el paquete –java.io.*– porque queremos tener acceso también a las
excepciones que se pudiesen presentar. En este método tenemos como atributos,
también estáticos, cadenas que corresponden a las claves de las carreras y a los
nombres de las mismas –lı́neas 15 y 20–. Podrı́amos tener, en lugar de las cadenas
correspondientes, una declaración de arreglos
public static int [ ] c a r r e r a s ;
public static String [ ] sCarreras ;
10.6 El catálogo de carreras 482

e ir agregando cada una de las claves con su correspondiente nombre. El único


problema hubiese sido la inicialización del arreglo, pues tendrı́amos primero que
leer para saber cuántas claves están registradas. También la localización de cada
clave hubiese sido un poco más engorrosa, pues habrı́a que hacer una búsqueda li-
neal del arreglo de claves en lugar de utilizar indexOf que tiene una implementación
muy eficiente.
El método central de la clase es leeCarreras en las lı́neas 42 a 67 del listado 10.1
en la página 479; recibe como parámetro una cadena que corresponde al nombre o
ruta de acceso del archivo en disco en donde se encuentran grabadas las carreras
de la Facultad de Ciencias. Este método es invocado la primera vez que cualquier
otro método de la clase sea invocado.
Declaramos un objeto de la clase Scanner y lo construimos “encima” de un
archivo en disco, localizado y abierto cuando creamos el objeto de la clase File en
la lı́nea 45 del listado 10.1 en la página 479. Es importante mencionar que esta
construcción no se puede hacer fuera del try porque el constructor del Scanner,
cuando se aplica a un objeto de la clase File, puede lanzar la excepción FileNot-
FoundException, que debe ser vigilada. La construcción de un objeto de la clase File
también puede lanzar una excepción, pero ésta es de la clase NullPointerException,
por lo que no tiene que ser vigilada.
La clase Scanner tiene varios constructores que se listan a continuación:

Scanner( File source) throws FileNotFoundException


Construye un objeto Scanner que produce valores obtenidos del archivo
especificado. Lanza la excepción si no puede encontrar el archivo especi-
ficado.
Scanner( File source , String charsetName)
throws FileNotFoundException
Construye un objeto Scanner que produce valores obtenidos del archivo
especificado. Espera la entrada codificada en el conjunto de caracteres
dados. Lanza la excepción si no puede encontrar el archivo especificado.
Scanner(InputStream source)
Construye un objeto Scanner que obtiene su entrada del flujo de entrada
especificado.
Scanner(InputStream source, String charsetName)
Construye un objeto Scanner que obtiene su entrada del flujo de entra-
da especificado. Espera el flujo codificado en el conjunto de caracteres
especificado.
483 Entrada y salida

Scanner(Readable source)
construye un Scanner que toma su entrada de un objeto de una clase que
implemente a la interfaz Readable.
Scanner(ReadableByteChannel source)
Construye un nuevo Scanner que produce valores obtenidos del canal es-
pecificado.
Scanner(ReadableByteChannel source, String charsetName)
Construye un nuevo Scanner que produce valores obtenidos del canal es-
pecificado. Los valores están codificados en el conjunto de caracteres es-
pecificado.
Scanner(String source)
Construye un nuevo Scanner que produce valores obtenidos de interpretar
la cadena especificada.

10.7 Redireccionamiento de in, out y err

Muchas veces queremos que los resultados de un programa, o los mensajes de


error, en lugar de ir a los dispositivos estándar (todos a la consola) se graben en
algún archivo en disco para poder examinarlos con calma. Además de la manera en
que Unix permite redireccionar la salida, podemos, desde el programa, conseguir
esto. Para ello contamos con métodos en java.lang.System que permiten hacerlo.
Ellos son:
p u b l i c f i n a l s t a t i c v o i d s e t I n ( I n p u t S t r e a m newIn )
p u b l i c f i n a l s t a t i c v o i d s e t O u t ( P r i n t S t r e a m newOut )
p u b l i c f i n a l s t a t i c v o i d s e t E r r ( P r i n t S t r e a m newErr )

A continuación dos ejemplos:


System . s e t I n ( new F i l e I n p u t S t r e a m ( " misdatos .txt" ) ) ;
System . s e t O u t ( new P r i n t S t r e a m ( new F i l e O u t p u t S t r e a m ( " misdatos .out" ) ) ) ;

En cuanto a System.in no es cómodo trabajar con este flujo. La razón es que,


como ya vimos, no tiene métodos poderosos para leer e interpretar datos, ya que
su proceso es byte por byte, o bien un número dado de bytes leı́dos a un arreglo
10.8 Persistencia de la base de datos 484

de bytes. Pero lo podemos usar como dispositivo en el que montemos algún otro
tipo de procesador de bytes o caracteres –como Scanner o BufferedReader– para
poder hacer lecturas de más alto nivel.
En cambio, tanto System.out como System.err, que son objetos de la clase
PrintStream interpretan representaciones internas a cadenas, usando los métodos
toString correspondientes. Son muy cómodos, sobre todo si se trata de interaccio-
nar con un usuario.

10.8 Persistencia de la base de datos

Hasta ahora únicamente hemos trabajado con la consola o bien con redirec-
cionamiento de la consola, pero no hemos entrado a la motivación principal de
este capı́tulo y que consiste en lograr guardar el estado de nuestra base de datos
para que pueda ser utilizado posteriormente como punto de partida en la siguiente
ejecución.
Podemos almacenar, en primera instancia, la base de datos como un conjunto
de cadenas, y para ello podemos volver a utilizar a los flujos BufferedReader y
BufferedWriter que son muy útiles para leer y escribir cadenas, pero en esta ocasión
queremos que el flujo subyacente sea un archivo en disco y no un flujo estándar.
Revisemos entonces la clase FileReader y FileWriter que me van a dar esa facilidad.
Estos flujos extienden a InputStreamReader y OutputStreamWriter respectivamente,
que a su vez heredan, respectivamente, de Reader y Writer. De esta jerarquı́a
únicamente hemos revisado la clase Reader, ası́ que procedemos a ver las otras
clases de la jerarquı́a que vamos a requerir.

public abstract class Writer implements Appendable,


Closeable, Flushable
Campo:
protected Object lock
Sincroniza el acceso a este flujo.
Constructores:
protected Writer()
Construye un flujo de salida de caracteres a ser sincronizado por él mismo.
protected Writer(Object lock)
Construye un flujo de caracteres que será sincronizado usando lock.
485 Entrada y salida

Métodos:
public Writer append(char c) throws IOException
Agrega el argumento al flujo this.
public Writer append(CharSequence csq) throws IOException
CharSequence es una interfaz de Java que básicamente proporciona méto-
dos para convertir una sucesión de caracteres, codificados en cualquier
código de 16 bits en cadenas o subsucesiones. Agrega el argumento al
flujo this.
public Writer append(CharSequence csq, int start , int end)
throws IOException
Agrega al flujo this la subsucesión de csq que empieza en start y termina
en el carácter inmediatamente a la izquierda de end.
public abstract void close () throws IOException
Cierra el flujo vaciándolo primero. Una vez cerrado, cualquier intento de
vaciarlo o escribir en él provocará una excepción de entrada/salida. No
importa que un flujo se intente cerrar una vez cerrado.
public abstract void flush () throws IOException
Provoca que todas las escrituras pendientes sean vaciadas al flujo pro-
porcionado por el sistema operativo. Sin embargo, el sistema operativo
podrı́a no vaciar su buffer.
public void write (int c) throws IOException
Escribe un único carácter en el flujo, tomando los 16 bits bajos del entero
proporcionado. Este método deberı́a ser sobreescrito por las subclases
correspondientes.
public void write (char[] cbuf) throws IOException
Escribe en el flujo los caracteres presentes en el arreglo cbuf.
public abstract void write(char [], int off , int len )
throws IOException
Escribe en el flujo el contenido del arreglo cbuf a partir del carácter en la
posición off y un total de len caracteres.
public void write ( String str ) throws IOException
Escribe el contenido de la cadena en el flujo.
public void write ( String str , int off , int len )
throws IOException
Escribe la subcadena de str desde la posición off un total de len caracteres.
10.8 Persistencia de la base de datos 486

Realmente el único método con el que hay que tener cuidado es el que escribe
un carácter (entero), porque el resto de los métodos se construyen simplemente
invocando a éste.
Las clases que heredan directamente de Reader y Writer son, respectivamente,
InputStreamReader y OutputStreamWriter que pasamos a revisar. Primero revisa-
remos la clase InputStreamReader.

public class InputStreamReader extends Reader


Hereda el campo lock de Reader y los métodos definidos en la superclase.
Listamos los que son redefinidos en esta subclase.
Constructores:
public InputStreamReader(InputStream in)
Construye un flujo de entrada sobre el flujo de bytes que se le proporcione
(y que debe existir como objeto). Básicamente va a traducir bytes en
caracteres.
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
Construye un flujo de entrada sobre un flujo de bytes. Va a traducir de
acuerdo al código nombrado en charsetName. Si el código no existe lanza
una excepción de código inválido.
public InputStreamReader(InputStream in, Charset cs)
Construye un flujo sobre in y que va a traducir los bytes de acuerdo al
código dado en cs.
Métodos:
public void close () throws IOException
Cierra el flujo correspondiente. Lanza una excepción si hay algún error de
entrada/salida.
public String getEncoding()
Regresa el nombre del código de traducción de bytes que es el usado por
este flujo.
public int read() throws IOException
Redefine el método correspondiente en Reader (recuérdese que en Reader
era un método abstracto).
public int read(char[] cbuf, int offset , int length)
throws IOException
Redefine el método correspondiente en Reader.
public boolean ready() throws IOException
Redefine el método ready en Reader.
487 Entrada y salida

Siguiendo en orden en la jerarquı́a, ahora revisaremos OutputStreamWriter.

public class OutputStreamWriter extends Writer


Hereda el campo lock de la superclase. Listaremos los constructores y los
métodos abstractos de la superclase que se implementan en esta clase.
Constructores:
public OutputStreamWriter(OutputStream out)
Construye un flujo de salida que va a convertir caracteres en bytes. Se
monta sobre un flujo de salida de bytes.
public OutputStreamWriter(OutputStream out, Charset cs)
Construye un flujo de salida de caracteres a bytes, montado sobre un flujo
de salida de bytes, out que usa la codificación dada por cs.
public OutputStreamWriter(OutputStream out,
CharsetEncoder enc)
Construye un OutputStreamWriter sobre un flujo de bytes out y que usa
la codificación dada por enc.
Métodos:
public void close () throws IOException
Cierra el flujo vaciando el buffer de salida. Lanza una excepción si tiene
problemas de entrada/salida.
public void flush () throws IOException
Implementa el método correspondiente en Writer.
public String getEncoding()
Regresa el nombre del código que se está usando para escribir los bytes
correspondientes a los caracteres en memoria.
public void write (int c) throws IOException
Implementa el método correspondiente en Writer.
public void write (char[] cbuf, int off int len )
throws IOException
Implementa al método correspondiente en Writer.
public void write ( String str , int off int len )
throws IOException
Implementa al método correspondiente en Writer.

Ahora sı́ ya podemos pasar a revisar las clases FileReader y FileWriter que here-
dan respectivamente de InputStreamReader y OutputStreamWriter. Empezaremos
10.8 Persistencia de la base de datos 488

por el flujo de entrada. En esta subclase únicamente se definen los constructores,


ya que se heredan precisa y exactamente los métodos implementados en Input-
StreamReader.

public class FileReader extends InputStreamReader


Dado que ésta es una subclase de InputStreamReader, va a leer bytes y
convertirlos en caracteres. Al igual que su superclase, también trabaja
sobre un InputStream.
Constructores:
public FileReader ( String fileName) throws FileNotFoundException
Crea un flujo para lectura de disco cuyo nombre es fileName. Si no en-
cuentra el flujo lanza una excepción de archivo no encontrado.
public FileReader ( File file ) throws FileNotFoundException
File es una representación abstracta de un archivo del sistema operativo
en cuestión, e incluye aspectos como definir el separador y terminador de
archivos, la ruta en el disco, la identificación del archivo en disco, etc.
(ver la definición de esta clase en la documentación de Java). Crea un
flujo para lectura identificado con file. Si no encuentra el flujo lanza una
excepción de archivo no encontrado.
public FileReader ( FileDescriptor fd)
throws FileNotFoundException
Un FileDescriptor es un objeto que describe a un archivo en disco (ver
documentación de Java). Crea un flujo de lectura desde disco, donde el
archivo está asociado al FileDescriptor. Si no lo encuentra, lanza una ex-
cepción de archivo no encontrado.
Métodos:
Los heredados de Reader y de InputStreamReader, que ya revisamos.

Para los flujos de salida que escriben a disco tenemos una situación similar a
la de archivos de entrada, pues lo único que se define para la subclase FileWriter
son los constructores. Para el resto de los métodos y campos se heredan las im-
plementaciones dadas por OutputStreamWriter. Veamos la definición.

public class FileWriter extends OutputStreamWriter


Enlaza a un flujo de salida de bytes con un archivo en disco. Lo único que
se implementa a nivel de esta subclase son los constructores.
489 Entrada y salida

Constructores:
FileWriter ( File file ) throws IOException
Construye un flujo a disco sobre el flujo file, que podrı́a ser, a su vez, un
FileWriter o cualquier OutputStream o subclases de ésta. La excepción la
lanza si file es un directorio y no un archivo, no existe pero no puede ser
creado o no puede ser abierto por cualquier otra razón.
FileWriter ( File file , boolean append) throws IOException
Construye un flujo a disco sobre el flujo file, que podrı́a ser, a su vez,
un FileWriter o cualquier OutputStream o subclases de ésta. La excepción
la lanza si file es un directorio y no un archivo, no existe pero no puede
ser creado o no puede ser abierto por cualquier otra razón. Si append es
verdadero, la escritura se realiza al final del archivo; si es falsa se realiza
al principio del archivo, como en el constructor sin parámetro booleano.
FileWriter ( FileDescriptor fd)
Construye un flujo a disco asociado con el FileDescriptor.
FileWriter ( String fileName) throws IOException
Construye el flujo dado un nombre. Lanza una excepción de entrada/salida
por las mismas causas que los otros constructores.
FileWriter ( String fileName, boolean append)
throws IOException
Construye el flujo dado un nombre. Si append es verdadera entonces escri-
be al final del archivo; si el archivo no existe lo crea. Lanza una excepción
de entrada/salida por las mismas causas que los otros constructores.

Hay que recordar que la herencia permite que dondequiera que aparezca una
clase como parámetro, los argumentos pueden ser objetos de cualquiera de sus sub-
clases. Con esto en mente pasamos a implementar las opciones en el menú de leer
de un archivo en disco o escribir a un archivo en disco para guardar la información
generada en una sesión dada.

10.8.1. Cómo guardar datos en un archivo en disco

La información en disco se guarda invariablemente en bytes. De hecho, un


archivo en disco es, simplemente, una sucesión muy grande de bytes, que puede
ser interpretado de muy diversas maneras: podemos tomar los bytes de cuatro en
10.8 Persistencia de la base de datos 490

cuatro e interpretar cada cuatro bytes como un entero; o podemos tomarlos de


seis en seis e interpretar a cada grupo como un double. En ningún otro caso es
más cierto el dicho de que todo depende del color del cristal con que se mira que
en la lectura (interpretación) de archivos en disco. Y esta interpretación depende
del enunciado con que se tenga acceso al archivo y el tipo de flujo que se utilice
para ello.
Como mencionamos al principio, por lo pronto escribiremos y leeremos (re-
cuperaremos) cadenas, representadas por sucesiones de bytes y separadas entre
sı́ por caracteres de fin de lı́nea. Dada esta situación utilizaremos para entrada
objetos de la clase FileReader y para salida objetos de la clase FileWriter.
Agregaremos a nuestro menú tres opciones nuevas, leer registros desde disco,
escribir registros a disco y agregar registros en disco a un archivo que ya tiene
información. Qué clase de archivo usar deberá ser una decisión que se toma una vez
elegida alguna de estas opciones. Consideramos que cualquiera de estas acciones
puede llevarse a cabo en cualquier momento del proceso y, por lo tanto, la elección
del archivo debe hacerse en el momento en que se elige la opción. La otra opción
pudiese ser elegir el archivo al entrar al proceso. Pero eso obligarı́a al usuario, ya
sea que quiera o no leer de/guardar en archivos de disco, proporcionar el nombre
para los mismos, algo que no consideramos adecuado. Este curso de acción también
evitarı́a que se pudiera leer o escribir, en una misma sesión, más de un archivo.
Por lo tanto, la definición del archivo a usar estará situada dentro de la opción
correspondiente.
Podemos implementar ya esta opción. Lo primero que hacemos es declarar tres
constantes simbólicas para usarlas en el switch, y que son:

s t a t i c f i n a l i n t AGREGA = 1 ,
...
LEER = 6 ,
GUARDAR = 7 ,
PEGARDISCO = 8 ;

La opción de guardar lo que llevamos en un archivo en disco debe seguir el


guión dado en la figura 10.10 en la página opuesta.
Para identificar el archivo que deseamos usar, algo que tendremos que hacer
en los tres casos, construimos un método que lea el nombre del archivo de disco
en consola, y que queda como se ve en el listado 10.2 en la página opuesta.
491 Entrada y salida

Figura 10.10 Algoritmo para guardar la base de datos en disco


$ $
'
' '
' Identificar archivo
'
' '
&
'
' Abrir el archivo
'
' Inicio
'
' '
'
' '
'
Colocarse al principio
'
' %
' de la base de datos
Guardar Base de '
&
Datos en archivo # #
'
'
en disco '
' Procesar registro Escribir registro en disco
'
' Proceso
'
' (mientras haya) Pasar al siguiente
'
'
'
'
'
'
'
' !
%Final Cerrar el archivo

Código 10.2 Método que solicita al usuario nombre de archivo MenuListaIO

29 /∗ ∗
30 ∗ P i d e a l u s u a r i o e l nombre d e l a r c h i v o en e l que d e s e a
31 ∗ e s c r i b i r o d e l que d e s e a l e e r .
32 ∗ @param c o n s D i s p o s i t i v o d e l que va a l e e r e l nombre
33 ∗ @param l e c t u r a True : l e c t u r a , f a l s e : e s c r i t u r a .
34 ∗ @return e l archivo s o l i c i t a d o .
35 ∗/
36 p u b l i c S t r i n g pideNombreArch ( B u f f e r e d R e a d e r cons , i n t c a s o )
37 throws I O E x c e p t i o n {
38 S t r i n g m e n s a j e = "Por favor dame el nombre del archivo \n"
39 + ( c a s o == LEER? "del que vas a leer registros "
40 : ( c a s o == GUARDAR
41 ? "en el que vas a guardar la base de datos "
42 : "en el que vas a agregar registros " ) )
43 + ":\t" ;
44 S t r i n g nombre ;
45 try {
46 System . o u t . p r i n t ( m e n s a j e ) ;
47 nombre = c o n s . r e a d L i n e ( ) ;
48 } catch ( I O E x c e p t i o n e ) {
49 throw e ;
50 } // end o f t r y  c a t c h
51 r e t u r n nombre ;
52 }

Al método pideNombreArch le pasamos el flujo que estamos usando para comu-


nicarnos con el usuario. Queremos que el mensaje sea preciso respecto a qué vamos
a hacer con el archivo, pero como usamos el mismo método simplemente le pasa-
mos de cuál caso se trata (caso) para que pueda armar el mensaje correspondiente
10.8 Persistencia de la base de datos 492

(lı́neas 38 a 43). Después entramos a un bloque try. . . catch en el que vamos a


leer del usuario el nombre del archivo. El método, como lo indica su encabezado,
exporta la excepción que pudiera lanzarse al leer el nombre del archivo. Estamos
listos ya para programar el algoritmo de la figura 10.10 en la página anterior. El
código lo podemos ver en el listado 10.3.

Código 10.3 Código para guardar la base de datos MenuListaIO

305 case GUARDAR:


306 try {
307 s A r c h i v o = pideNombreArch ( cons ,GUARDAR ) ;
308 a r c h i v o O u t = new B u f f e r e d W r i t e r
309 ( new F i l e W r i t e r ( s A r c h i v o ) ) ;
310 System . o u t . p r i n t l n ( "Abri archivo " ) ;
311 E s t u d i a n t e l i s t a = ( ( E s t u d i a n t e ) miCurso . g e t L i s t a ( ) ) ;
312 w h i l e ( l i s t a != n u l l ) {
313 a r c h i v o O u t . w r i t e ( l i s t a . daNombre ( ) ) ;
314 archivoOut . newLine ( ) ;
315 a r c h i v o O u t . w r i t e ( l i s t a . daCuenta ( ) ) ;
316 archivoOut . newLine ( ) ;
317 archivoOut . write ( l i s t a . daCarrera ( ) ) ;
318 archivoOut . newLine ( ) ;
319 archivoOut . w r i t e ( l i s t a . daClave ( ) ) ;
320 archivoOut . newLine ( ) ;
321 System . o u t . p r i n t l n ( l i s t a . daNombre ()+"\n" ) ;
322 System . o u t . p r i n t l n ( l i s t a . daCuenta ()+"\n" ) ;
323 System . o u t . p r i n t l n ( l i s t a . d a C a r r e r a ()+"\n" ) ;
324 System . o u t . p r i n t l n ( l i s t a . d a C l a v e ()+"\n" ) ;
325 l i s t a = l i s t a . getSiguiente ();
326 } // end o f w h i l e ( l i s t a != n u l l )
327 archivoOut . f l u s h ( ) ;
328 archivoOut . c l os e ( ) ;
329 } catch ( I O E x c e p t i o n e ) {
330 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
331 } // end o f t r y  c a t c h
332 finally {
333 try {
334 i f ( a r c h i v o O u t != n u l l ) {
335 archivoOut . c l os e ( ) ;
336 } // end o f i f ( a r c h i v o O u t != n u l l )
337 } catch ( I O E x c e p t i o n e ) {
338 System . e r r . p r i n t l n ( "No pude cerrar el archivo " ) ;
339 } // end o f t r y  c a t c h
340 } // end o f f i n a l l y
341 r e t u r n GUARDAR;
493 Entrada y salida

Colocamos toda la opción en un bloque try. . . catch porque queremos suspender


en cuanto se presente una primera excepción, ya sea que no podemos abrir el
archivo o que haya algún registro que no podemos escribir. El bloque tiene cláusula
finally – lı́neas 332 a 340 – para que en caso de que haya algún problema se proceda
a cerrar el archivo. Se verifica antes de intentar cerrarlo que el archivo exista y se
haya logrado abrir.

En las lı́neas 307 y 308 solicitamos el nombre del archivo a usar y procedemos
a abrir el archivo. En este punto la única excepción que pudo haber sido lanzada
es en la interacción con el usuario, ya que la apertura de un archivo en disco
difı́cilmente va a lanzar una excepción.

En la lı́nea 311 nos colocamos al principio de la lista. Como el método mi-


Curso.daLista() regresa un objeto de tipo Object tenemos que hacer un casting.
Una vez que estamos al principio de la lista, procedemos a escribir registro por
registro – lı́neas 313 a 320–. Escribimos campo por campo, en el orden en que
están en el registro, separando los campos entre sı́ por un carácter de fin de lı́nea
– archivoOut.newLine() – propio del sistema operativo en el que esté trabajando la
aplicación. En las lı́neas 321 a 324 simplemente se hace eco de lo que se escribió en
el disco como medida de verificación.

En seguida se pasa al siguiente registro para procesarlo de la misma manera.


Al terminar simplemente se cierra el archivo, haciendo persistente el contenido de
la lista en memoria.

10.8.2. Cómo leer registros de un archivo de disco

El algoritmo para leer registros de una archivo en disco es la imagen del proceso
para guardar. Éste se puede ver en la figura 10.11.

Para identificar el archivo del que vamos a leer usamos el mismo método,
excepto que con un mensaje apropiado. Al abrir el archivo automáticamente nos
encontraremos frente al primer registro. A partir de ahı́, suponemos que el archivo
está correcto y que hay cuatro cadenas sucesivas para cada registro que vamos a
leer. El código que corresponde a esta opción se encuentra en el listado 10.4 en la
siguiente página.
10.8 Persistencia de la base de datos 494

Figura 10.11 Algoritmo para leer registros desde disco

$ $
'
' '
' Identificar archivo
'
' '
&
'
' Abrir el archivo
'
' Inicio
'
' '
'
' '
'
Colocarse al prin-
'
' %
'
' cipio del archivo
Leer registros &
a la Base de Datos # #
'
desde un archivo en disco '
'
' Procesar registro Leer registro de disco
'
' P roceso
'
' (mientras haya) Pasar al siguiente
'
'
'
'
'
'
'
' !
%F inal Cerrar el archivo

Código 10.4 Opción para leer registros desde disco MenuListaIO (1/2)

234 case LEER : // L e e r de d i s c o


235 try {
236 s A r c h i v o = pideNombreArch ( cons , LEER ) ;
237 a r c h i v o I n = new B u f f e r e d R e a d e r ( new F i l e R e a d e r ( s A r c h i v o ) ) ;
238
239 w h i l e ( ( nombre = a r c h i v o I n . r e a d L i n e ( ) ) != n u l l ) {
240 cuenta = a r c h i v o I n . readLine ( ) ;
241 carrera = archivoIn . readLine ( ) ;
242 clave = archivoIn . readLine ( ) ;
243
244 miCurso . a g r e g a E s t F i n a l
245 ( new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ) ;
246 } // end o f w h i l e ( ( nombre = a r c h i v o I n . r e a d L i n e ( ) ) != n u l l )
247 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
248 System . o u t . p r i n t l n ( "El archivo " + s A r c h i v o
249 + " no existe ." ) ;
250 throw e ;
251 } catch ( I O E x c e p t i o n e ) {
252 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
253 } catch ( E x c e p t i o n e ) {
254 System . o u t . p r i n t l n ( "NO alcanzaron los datos " ) ;
255 i f ( c a r r e r a == n u l l ) {
256 c a r r e r a = "????" ;
257 System . o u t . p r i n t l n ( "No hubo carrera " ) ;
258 } // end o f i f ( c a r r e r a == n u l l )
495 Entrada y salida

Código 10.4 Opción para leer registros desde disco MenuListaIO (2/2)

234 i f ( c u e n t a == n u l l ) {
235 c u e n t a = " 000000000 " ;
236 System . o u t . p r i n t l n ( "No hubo cuenta " ) ;
237 } // end o f i f ( c u e n t a == n u l l )
238 i f ( c l a v e == n u l l ) {
239 c l a v e = "????" ;
240 System . o u t . p r i n t l n ( "No hubo clave " ) ;
241 } // end o f i f ( c l a v e == n u l l )
242 } // end o f c a t c h
243 finally {
244 i f ( a r c h i v o I n != n u l l ) {
245 try {
246 archivoIn . close ();
247 } catch ( I O E x c e p t i o n e ) {
248 System . e r r . p r i n t l n ( "No pude cerrar el"
249 + " archivo de lectura " ) ;
250 } // end o f t r y  c a t c h
251 } // end o f i f ( a r c h i v o I n != n u l l )
252 } // end o f f i n a l l y
253 r e t u r n LEER ;

Nuevamente tenemos que encerrar nuestro proceso en un bloque try. . . catch,


ya que ası́ nos lo exigen los métodos de entrada/salida que estamos utilizando. El
proceso principal, si todo marcha bien, consiste en leer el nombre del archivo en la
lı́nea 239 y luego proceder a abrirlo. Una vez abierto el archivo se van a leer cuatro
cadenas para considerarlas como los datos para un registro de estudiante, el cual
se agrega a la base de datos en las lı́neas 244 y 245. Conforme se van leyendo las
cadenas va avanzando el archivo, por lo que no hay necesidad de avanzarlo. Sin
embargo, al leer el nombre sı́ verificamos si se alcanzó el fin de archivo; estamos
suponiendo que los registros vienen completos en grupos de cuatro cadenas y en el
orden en que se intentan leer. En la lı́nea 239 se verifica que no se haya alcanzado
el fin de archivo; si se alcanzó el fin de archivo, el método regresará una referencia
nula.
Podemos encontrarnos con varios errores en este proceso. El primero de ellos es
que pretendamos leer de un archivo que no existe. En este caso se lanza una excep-
ción de la clase FileNotFoundException que manejamos parcialmente: escribimos
un mensaje y exportamos la excepción, ya que no se va a poder hacer nada.
El siguiente error que podrı́amos tener en nuestro proceso es que los grupos
de cuatro cadenas no estén completos y no haya en el archivo un múltiplo de
cuatro en el número de las cadenas. En este caso, al intentar leer cadenas nos
encontraremos más allá del fin de archivo – lı́neas 242 a 245 – lo que lanzará una
10.8 Persistencia de la base de datos 496

excepción de la clase IOException, que es atrapada en la lı́nea 251 y manejada


simplemente no agregando ese registro incompleto y absorbiendo la excepción.
La ausencia de suficientes datos también puede lanzar una excepción de ti-
po aritmético, lo que preveremos en la lı́nea 253 donde tratamos de averiguar
cuáles fueron los datos que no pudimos leer, mandando el mensaje adecuado y
absorbiendo la excepción.
Sin importar si hubo una excepción o no trataremos de cerrar el archivo si es
que éste se abrió, mediante una cláusula finally – en las lı́neas 243 a 252– en la
que, si al cerrar el archivo se lanzó una excepción, ésta se absorbe después del
mensaje correspondiente.

10.8.3. Cómo agregar a un archivo ya creado

El algoritmo para agregar registros a un archivo creado previamente es exacta-


mente igual que el que crea un archivo nuevo, excepto que al abrir el archivo hay
que indicar que se busca uno que ya existe; adicionalmente, en lugar de “escribir”
en el archivo procedemos a agregar (append ). Usando entonces el mismo algorit-
mo que para guardar, con los cambios que acabamos de mencionar, el código para
esta opción queda como se muestra en el listado 10.5.

Código 10.5 Opción de agregar registros a un archivo en disco MenuListaIO (1/2)

300 case PEGARDISCO :


301 try {
302 s A r c h i v o = pideNombreArch ( cons , PEGARDISCO ) ;
303 a r c h i v o O u t = new B u f f e r e d W r i t e r
304 ( new F i l e W r i t e r ( s A r c h i v o , t r u e ) ) ;
305 E s t u d i a n t e l i s t a = ( ( E s t u d i a n t e ) miCurso . d a L i s t a ( ) ) ;
306 w h i l e ( l i s t a != n u l l ) {
307 a r c h i v o O u t . append ( l i s t a . daNombre ( ) ) ;
308 archivoOut . newLine ( ) ;
309 a r c h i v o O u t . append ( l i s t a . daCuenta ( ) ) ;
310 archivoOut . newLine ( ) ;
311 a r c h i v o O u t . append ( l i s t a . d a C a r r e r a ( ) ) ;
312 archivoOut . newLine ( ) ;
313 a r c h i v o O u t . append ( l i s t a . d a C l a v e ( ) ) ;
314 archivoOut . newLine ( ) ;
497 Entrada y salida

Código 10.5 Opción de agregar registros a un archivo en disco MenuListaIO (2/2)

300 System . o u t . p r i n t l n ( l i s t a . daNombre ()+"\n" ) ;


301 System . o u t . p r i n t l n ( l i s t a . daCuenta ()+"\n" ) ;
302 System . o u t . p r i n t l n ( l i s t a . d a C a r r e r a ()+"\n" ) ;
303 System . o u t . p r i n t l n ( l i s t a . d a C l a v e ()+"\n" ) ;
304 l i s t a = l i s t a . daSiguiente ();
305 } // end o f w h i l e ( l i s t a != n u l l )
306 archivoOut . f l u s h ( ) ;
307 archivoOut . c l os e ( ) ;
308 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
309 System . e r r . p r i n t l n ( "El archivo " + s A r c h i v o
310 + "no existe !!" ) ;
311 throws e ;
312 } catch ( I O E x c e p t i o n e ) {
313 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
314 } // end o f t r y  c a t c h
315 finally {
316 try {
317 i f ( a r c h i v o O u t != n u l l ) {
318 archivoOut . c l os e ( ) ;
319 } // end o f i f ( a r c h i v o O u t != n u l l )
320 } catch ( I O E x c e p t i o n e ) {
321 System . e r r . p r i n t l n ( "No pude cerrar el archivo " ) ;
322 } // end o f t r y  c a t c h
323 } // end o f f i n a l l y
324 r e t u r n PEGARDISCO ;

Los únicos cambios en esta opción son:

En las lı́neas 303 y 304, donde se usa otro constructor para el flujo, el que
permite indicar si se usa un archivo ya existente para agregar a él.

En las lı́neas 307 a 313 se usa el método append en lugar del método write,
ya que deseamos seguir agregando al final del archivo.

En la lı́nea 308 se trata de atrapar una excepción de archivo no encontrado,


ya que en el caso de querer agregar a un archivo éste debe existir, mien-
tras que en el contexto de crear un archivo nuevo, esta excepción no puede
presentarse.

Como se pudo observar, el manejo de flujos en Java tiene un trato uniforme


y, una vez que se manejan las excepciones, no representa mayor problema. Un
punto que hay que vigilar, sin embargo, es la construcción de flujos que se montan
sobre otros flujos. Acá hay que tener cuidado en usar el constructor adecuado
10.9 Escritura y lectura de campos que no son cadenas 498

y proporcionar un flujo que pertenezca a la clase que indica el constructor. La


elección del flujo adecuado depende de qué es lo que queremos hacer con él y
cuáles son las operaciones más frecuentes que esperamos. Los flujos que hemos
usado hasta ahora manejan de manera idónea lectura y escritura de cadenas, pero
puede suceder que ésta no sea la operación más frecuente o la que busquemos
facilitar.
Un comentario final: cuando en nuestra aplicación requerimos de flujos para
montar en ellos al flujo final con el que querı́amos, los construimos “al vuelo”, es
decir, de manera anónima sin asignarles un identificador. Todas estas construc-
ciones las podı́amos haber hecho en dos pasos, primero construir el archivo más
primitivo (el que tiene contacto con el sistema) asignándole un identificador y
posteriormente construir el flujo de nuestra aplicación. Como no se utiliza el ar-
chivo base de ninguna otra manera más que para establecer la conexión no vimos
necesario hacer esto último.

10.9 Escritura y lectura de campos que no son cadenas

Supongamos que queremos tener en el disco una imagen similar a nuestra


primera implementación de la base de datos, una sucesión de caracteres, donde
sabemos donde empieza un registro y termina el otro porque contamos caracteres
(o bytes). Supongamos también que nuestro archivo es, de alguna manera, auto-
descriptivo, esto es que tendrá como encabezado del archivo (primera información)
el número de campos y el tamaño de cada uno de ellos. De lo anterior, nuestro
archivo se verı́a como se muestra en la figura 10.12. Mostramos el contenido de
cada uno de los bytes2 en hexadecimal (4 bits para cada sı́mbolo). Por ejemplo,
el encabezado de este archivo nos indica que cada registro consiste de cuatro (4)
campos; el primer campo (nombre) ocupa 40 bytes; el segundo campo (cuenta)
ocupa nueve bytes; el tercer campo (carrera) ocupa cuatro bytes; el cuarto campo
(clave) ocupa 20 bytes. Cabe aclarar que la denominación entre paréntesis está fue-
ra de la aplicación; simplemente lo anotamos para tener una idea más clara de
qué es lo que estamos haciendo. Como los enteros que estamos manejando son
relativamente pequeños, los guardaremos en variables tipo short, que ocupa cada
una dos bytes. Las lı́neas punteadas indican la “máscara” que estamos aplicando
al archivo (cómo interpretamos la información) y el valor dentro de estas celdas
indica el número en base 10 que está grabado. Abajo de cada celda se encuentra
la posición del byte correspondiente en el archivo, empezando de 0 (cero).
499 Entrada y salida

Figura 10.12 Formato de un archivo binario autodescrito

Núm de tamaño tamaño tamaño tamaño Primer campo


campos campo1 campo2 campo3 campo4
hkkkikkkjhkkkikkkjhkkkikkkjhkkkikkkjhkkkikkkjhkkkkkkkkkkkkkkkkkkkkkkkkkikkkkkkkkkkkkkkkkkkkkkkkkkj
short short short short short

410 4010 910 410 2010 A r i z ...

0 0 0 4 0 0 2 8 0 0 0 9 0 0 0 4 0 0 1 4 4 3 7 2 6 9 7A . .... .
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 . . . . . . 49

Lo que conviene es que la clase para cada registro nos entregue el tamaño de
cada campo. Podemos suponer que esto es ası́, agregando a la clase InfoEstudiante
un arreglo con esta información:
s h o r t [ ] tamanhos = { 4 , 4 0 , 9 , 4 , 2 0 } ;
quedando en tamanhos[0] el número de campos, en tamanhos[1] el tamaño del primer
campo y ası́ sucesivamente. Agregamos a la clase un método
p u b l i c s h o r t getTamanho ( i n t campo ) {
r e t u r n tamanhos [ campo ] ;
}
que simplemente regresa el tamaño del campo solicitado.
Podemos pensar en un archivo que es heterogéneo, en el sentido de que lo que
llamamos el encabezado del mismo no tiene la forma que el resto de los elementos;
éstos se componen de n campos – la n viene en los primeros dos bytes del archivo
con formato binario de un entero corto (short) – con un total de k bytes que
corresponde a la suma de los n enteros cortos que aparecen a partir del byte 2 del
archivo. El encabezado del archivo consiste de 2pn 1q bytes. Una vez procesados
estos n 1 enteros cortos, el resto del archivo lo podemos ver como un arreglo
unidimensional de bytes (similarmente a como manejamos la base de datos en
cadenas al principio).
Deseamos insistir en lo que dijimos al principio de esta sección: todos los
archivos en disco se componen de bytes; la manera de agrupar los bytes para
obtener información que tenga sentido depende del software que se use para verlo,
de las máscaras que le apliquemos al archivo. Una vez que terminemos de armar
nuestro archivo con el formato que acabamos de ver, podrán observar el archivo
con alguno de los visores de su sistema operativo y verán que también los primeros
2pn 1q bytes podrı́an tratar de interpretarlos como caracteres ASCII, no como
variables de Java; por supuesto que si hacen esto la mayorı́a de estos caracteres
no se podrán ver en pantalla (por ejemplo, el 0 binario) o aparecerán caracteres
que no guardan ninguna relación con lo que ustedes esperarı́an ver.
10.9 Escritura y lectura de campos que no son cadenas 500

Como queremos escribir y leer “binario” (imágenes de variables de Java) para el


encabezado del archivo, usaremos el flujo que nos permite hacer esto directamente
y que es DataOutputStream y DataInputStream respectivamente. Esta última ya la
revisamos en la página 467, por lo que pasamos a revisar la clase DataOutputS-
tream, aunque van a ver que corre paralela al flujo correspondiente de entrada.

public class DataOutputStream extends FilterOutputStream


implements DataOutput
Campos:
protected int written
Indica el número de bytes que se han escrito sobre este flujo hasta el
momento.
Constructores:
public DataOutputStream(OutputStream out)
Construye sobre un flujo de salida que esté conectado a algún dispositivo.
Éste es el único constructor de este flujo.
Métodos:
public void flush () throws IOException
Vacı́a el buffer de los bytes almacenados. Usa para ello el método dado
por el flujo de salida dado como argumento en el constructor.
public final int size ()
Regresa el valor del campo written.
public void write (byte[] b, int off , int len )throws IOException
Escribe la porción del arreglo de bytes b que empieza en off y tiene un
tamaño máximo de len bytes. Si no se lanza una excepción, written se
incrementa en len unidades.
void write (int b) throws IOException
Implementa el método write de la clase OutputStream.
public final void write XXX ( Y Y Y par) throws IOException
Esta denominación incluye en realidad a varios métodos, que toman la
representación interna del tipo Y Y Y y lo transfieren tal cual al flujo de
salida. A continuación damos las combinaciones de XXX y Y Y Y que
tenemos en los distintos métodos:
501 Entrada y salida

XXX YYY Descripción


Boolean boolean Escribe una booleana como un valor de
1 byte.
Byte int Escribe un byte que corresponde a la par-
te baja del entero
Bytes String Escribe la cadena como una sucesión de
bytes.
Char int Escribe un carácter (los 2 bytes más ba-
jos del entero), el byte alto primero.
Chars String Escribe la cadena como una sucesión de
caracteres (2 bytes por carácter).
Double double Convierte el double a un long usando
el método doubleToLongBits de la clase
Double y luego escribe el valor obtenido
como una sucesión de 8 bytes, el byte alto
primero.
Float float Convierte el valor float a un valor entero
(int) usando el método floatToIntBits de
la clase Float para luego escribirlo como
u7n entero de 4 bytes, el byte alto prime-
ro.
Int int Escribe un entero en 4 bytes, byte alto
primero.
Long long Escribe el entero largo en 8 bytes, byte
alto primero.
Short int Escribe el entero corto en 2 bytes (los
dos bytes más bajos del entero) byte alto
primero.
UTF String Escribe una cadena en el flujo usando co-
dificación UTF-8 modificada de manera
que es independiente de la computadora.
Como se puede ver, este flujo sirve para escribir en disco imágenes (copias)
del contenido de variables en memoria, siguiendo el patrón de bits dado
para su codificación binaria. Por esta última caracterización, a los archivos
creados con este tipo de flujos se les conoce como archivos binarios, esto
es, que los bytes deben interpretarse como si fueran variables en memoria.
10.9 Escritura y lectura de campos que no son cadenas 502

Es en este tipo de archivos donde realmente se puede utilizar el método skip,


ya que el número de bytes que componen un registro lógico (que depende de la
manera como lo tratemos de leer) es constante.
Conocemos ya todo lo que requerimos para proponer una nueva opción en
nuestro menú, la que escribe y lee archivos binarios. Revisemos el código agregado
o modificado que se encuentra en los listados 10.6 para lo que tiene que ver con el
proceso de la opción y el listado 10.7 en la página opuesta para lo que tiene que
ver con la opción misma.

Código 10.6 Declaraciones de flujos binarios MenuListaIO

6 s t a t i c f i n a l i n t AGREGA = 1 ,

......
15 LEERREGS = 9 ,
16 GUARDARREGS = 1 0 ;

......
128 p u b l i c i n t daMenu ( B u f f e r e d R e a d e r cons , L i s t a C u r s o miCurso )
129 throws I O E x c e p t i o n {

......
141 DataInputStream a r c h i v o R e g s I n = n u l l ;
142 DataOutputStream a r c h i v o R e g s O u t = n u l l ;

......
156 + "(9)\ tLeer de archivo binario \n"
157 + "(A)\ tGuardar en archivo binario \n"

......
170 o p c i o n = " 0123456789 AB" . i n d e x O f ( s o p c i o n ) ;

......

Nuevamente optamos por declarar los flujos necesarios dentro del método que
maneja el menú. La razón de esto es que estas opciones se pueden elegir en cual-
quier momento y más de una vez, en cada ocasión con flujos fı́sicos distintos, por lo
que hacerlos globales a la clase o, peor aún, al uso de la clase, amarrarı́a a utilizar
únicamente el flujo determinado antes de empezar, cuando existe la posibilidad
de que no se elija esta opción o que, como ya mencionamos, se desee hacer varias
copias de l;a información. En las lı́neas 15, 16 156 a 170 simplemente agregamos
dos opciones al menú, y los mecanismos para manejarlas – posponemos por el
momento el desarrollo de la opción correspondiente dentro del switch –. La decla-
ración de los flujos la hacemos en las lı́neas 141 y 142. Tanto en este caso como en
503 Entrada y salida

el los flujos BufferedReader y BufferedWriter podrı́amos haberlos declarado como


objetos de las superclases correspondiente:

134 Reader a r c h i v o I n = n u l l ;
135 Writer archivoOut = null ;

141 InputStream archivoRegsIn = null ;


142 OutputStream a r c h i v o R e g s O u t = n u l l ;

y determinar la subclase correspondiente en el momento de construirlos. De haber-


lo hecho ası́, para usar los métodos que se agregaron al nivel de Buffered. . . – como
readLine y writeLine – y Data. . . Stream – como readShort y writeShort – hubiése-
mos tenido que hacer casting para que el compilador los identificara. Pero, aun
cuando requerimos básicamente un flujo de entrada y uno de salida, no podrı́amos
tener nada más una declaración por función ya que la superclase común a Reader
e InputStream es Object, y el usar a Object en la declaración hubiese requerido de
casting prácticamente en cada momento de uso de los flujos. La asignación de un
valor null en la declaración es para detectar, en su caso, si el flujo pudo construirse
o no. Adicionalmente, dadas las caracterı́sticas de las excepciones, donde puede
haber código que no se ejecute, en el bloque catch no se tiene la seguridad de que
a las variables se les haya asignado efectivamente un valor en el bloque try, por lo
que el compilador no va a permitir que las variables inicien sin valor asignado.
En las lı́neas 156 y 157 del listado 10.6 simplemente agregamos las opciones
correspondientes al menú que se despliega, mientras que en la lı́nea 170 del mismo
listado modificamos para que estas opciones sean reconocidas.

Código 10.7 Opciones de leer y escribir a archivo binario MenuListaIOReg (1/3)

379 case LEERREGS :


380 try {
381 s A r c h i v o = pideNombreArch ( cons , LEER ) ;
382 a r c h i v o R e g s I n = new D a t a I n p u t S t r e a m
383 ( new F i l e I n p u t S t r e a m ( s A r c h i v o ) ) ;
384 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
385 System . e r r . p r i n t l n ( "el archivo de entrada "
386 + ( s A r c h i v o != n u l l ? s A r c h i v o : "nulo" )
387 + " no existe " ) ;
388 throw e ;
389 } // end o f t r y  c a t c h
10.9 Escritura y lectura de campos que no son cadenas 504

Código 10.7 Opciones de leer y escribir a archivo binario MenuListaIOReg (2/3)

390 try {
391 s h o r t tam = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
392 tamanhos = new s h o r t [ tam + 1 ] ;
393 tamanhos [ 0 ] = tam ;
394 f o r ( i n t i = 1 ; i <= tam ; i ++)
395 tamanhos [ i ] = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
396 s h o r t maxt = 0 ;
397 f o r ( i n t i = 0 ; i <= tamanhos [ 0 ] ; i ++)
398 maxt =( s h o r t ) ( Math . max ( maxt , tamanhos [ i ] ) ) ;
399 bCadena = new byte [ maxt ] ;
400 w h i l e ( a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) > 0 ) {
401 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
402 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
403 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
404 switch ( i ) {
405 case 2 :
406 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
407 break ;
408 case 3 :
409 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
410 break ;
411 case 4 :
412 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
413 break ;
414 default :
415 break ;
416 } // end o f s w i t c h ( i )
417 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
418 i f ( miCurso == n u l l ) {
419 System . o u t . p r i n t l n ( "No existe miCurso " ) ;
420 throw new N u l l P o i n t e r E x c e p t i o n ( " Uuups " ) ;
421 } // end o f i f ( miCurso == n u l l )
422 miCurso . a g r e g a E s t F i n a l
423 ( new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ) ;
424 } // end o f w h i l e ( a r c h i v o R e g s I n . r e a d ( bCadena
425 } catch ( I O E x c e p t i o n e ) {
426 System . e r r . p r i n t l n ( " Error de I/O" ) ;
427 throw e ;
428 } // end o f t r y  c a t c h
429 finally {
430 try {
431 archivoRegsIn . close ();
432 } catch ( I O E x c e p t i o n e ) {
433 System . e r r . p r i n t l n ( "No se pido cerrar el archivo " ) ;
434 } // end o f t r y  c a t c h
435 } // end o f f i n a l l y
436 r e t u r n LEERREGS ;
505 Entrada y salida

Código 10.7 Opciones de leer y escribir a archivo binario MenuListaIOReg (3/3)

437 case GUARDARREGS :


438 try {
439 s A r c h i v o = pideNombreArch ( cons ,GUARDAR ) ;
440 a r c h i v o R e g s O u t = new DataOutputStream
441 ( new F i l e O u t p u t S t r e a m ( s A r c h i v o ) ) ;
442 E s t u d i a n t e l i s t a = ( ( E s t u d i a n t e ) miCurso . d a L i s t a ( ) ) ;
443 i f ( l i s t a != n u l l ) {
444 tamanhos = l i s t a . getTamanhos ( ) ;
445 } // end o f i f ( l i s t a != n u l l )
446 else {
447 System . o u t . p r i n t l n ( "No hay nadie en la base"
448 + " de datos " ) ;
449 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Lista vacı́a " ) ;
450 } // end o f e l s e
451
452 a r c h i v o R e g s O u t . w r i t e S h o r t ( tamanhos [ 0 ] ) ;
453 f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++) {
454 a r c h i v o R e g s O u t . w r i t e S h o r t ( tamanhos [ i ] ) ;
455 } // end o f f o r ( i n t i = 1 ; . . .
456 // Ahora p r o c e d e m o s a v a c i a r l a b a s e de d a t o s
457 w h i l e ( l i s t a != n u l l ) {
458 f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++) {
459 nombre = ( l i s t a . daCampo ( i ) + b l a n c o s ) .
460 s u b s t r i n g ( 0 , tamanhos [ i ] ) ;
461 System . o u t . p r i n t l n ( l i s t a . daCampo ( i )+"\t"
462 +i ) ;
463 a r c h i v o R e g s O u t . w r i t e B y t e s ( nombre ) ;
464 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
465 l i s t a = l i s t a . daSiguiente ();
466 } // end o f w h i l e ( l i s t a != n u l l )
467 archivoRegsOut . f l u s h ( ) ;
468 archivoRegsOut . c l o s e ( ) ;
469 } catch ( I O E x c e p t i o n e ) {
470 System . e r r . p r i n t l n ( "No se qué pasó" ) ;
471 throw new I O E x c e p t i o n ( "Algo salió mal" ) ;
472 } // end o f t r y  c a t c h
473 r e t u r n GUARDARREGS ;

Como se puede observar, las dos opciones son prácticamente paralelas, excepto
que cuando una escribe la otra lee. Pasemos a revisar primero la opción de escri-
tura, que serı́a el orden en que programarı́amos para probar nuestra aplicación.
10.9 Escritura y lectura de campos que no son cadenas 506

10.9.1. Escritura en archivos binarios

Figura 10.13 Algoritmo para escribir archivo binario


$ #
'
' Pedir nombre de archivo
'
' Abrir el archivo
'
' Construirlo
'
'
'
'
'
'
'
' $
'
' '
'
' &
'
'
'
'lista no vacı́a Obtener descriptor de campos
'
' '
%
'
'
'
' À
'
'
'
' #
'
'
'
' Mensaje de lista vacı́a
'
' lista no vacı́a
'
'
'
' Lanzar excepción de argumento inválido
'
'
'
'
'
' $
'
Armar '
& '
'
'
Escribir número de
'
'
archivo Escribir '
& campos en binario
'
' $
binario '
' '
' descriptor
' '
Escribir tamaño &
'
' '
'
'
' de campos '
' de campo Escribir campoi
' '  '
descriptorr0s
'
' % %
'
' en binario
'
'
'
'
'
' $
'
' ' Escribir nombre ajustado con blancos
'
' '
'
'
' '
'
'
' '
&Escribir cuenta ajustada con blancos
'
' Procesar registro
'
'  Escribir carrera ajustada con blancos
'
' '
'
' mientras haya '
'
'
' '
' Escribir clave ajustada con blancos
'
' '
%
'
' Pasar al siguiente de la lista
'
'
'
'
'
'
'
%Cerrar el archivo

El algoritmo que se usó para escribir un archivo binario se encuentra en la figura


10.13 y corresponde a la discusión de la página 498 y mostrado en la figura 10.12
en la página 499.
En las lı́neas 440 a 442 del listado 10.7 se implementa la parte correspondiente
a abrir el archivo seleccionado mediante el método pideNombreArch. Como este
507 Entrada y salida

enunciado puede lanzar una excepción de entrada/salida se enmarca en un bloque


try. Sin embargo es difı́cil que la excepción sea lanzada – y en el caso de que
esto suceda quiere decir que hay problemas que no puede resolver el usuario –
no se conforma un bloque try sólo para esta operación. El constructor de la clase
DataOutputStream requiere de un flujo de salida (OutputStream) que realice la
conexión fı́sica. En este caso le pasamos como argumento un objeto de la clase
FileOutputStream construido al vuelo, y que se construye utilizando el nombre del
archivo deseado. Como FileOutputStream es una subclase de OutputStream no hay
ningún problema ni de compilación ni de ejecución.

En las lı́neas 443 a 450 se verifica que la lista en memoria no esté vacı́a. De ser
ası́ se procede a obtener el descriptor de los campos (el encabezado del archivo
binario) en un arreglo de enteros pequeños (short); si la lista está vacı́a se sale del
menú lanzando una excepción, que es atrapada desde el método principal (main)
de la aplicación.

Se procede a escribir el encabezado del archivo binario en las lı́neas 452 a 455
como lo indica el diagrama del algoritmo, utilizando para ello el método writeShort
de la clase DataOutputStream – ver documentación de la clase en las páginas 10.9
y 10.9 –. Una vez hecho esto se procede a escribir cada uno de los registros de la
base de datos, como un arreglo de bytes, con cada campo ocupando el número de
bytes que indica el encabezado del archivo – en las lı́neas 459 y 460 se ajusta el
campo a su tamaño agregando blancos y en la lı́nea 463 se escribe utilizando el
método writeBytes de la clase DataOutputStream, que convierte una cadena a un
arreglo de bytes –. Para escribir toda la lista seguimos nuestro algoritmo usual
que recorre listas.

Finalmente en las lı́neas 467 y 468 se procede a cerrar el archivo. En el caso de


archivos de disco esto es mucho muy importante, pues si no se hace el archivo no
pasa a formar parte del sistema de archivos y por lo tanto no existirá más allá de
la ejecución de la aplicación.

10.9.2. Lectura de archivos binarios

Como ya mencionamos antes, la lectura desde un archivo binario corre prácti-


camente paralelo a la escritura, pues se tiene que usar la misma máscara para
leer que la que se utilizó para escribir. En la figura 10.14 mostramos en detalle el
algoritmo para esta operación.
10.9 Escritura y lectura de campos que no son cadenas 508

Figura 10.14 Algoritmo para leer de archivo binario


$ $
'
' '
'Pedir nombre de archivo
'
' '
' $ $
'
' '
' '
'
' ' '
' &
' '
' '
'
'
' '
' ' Se localizó
'
' & '
' ∅
' '
& '
%
'
' Abrir el archivo Localizarlo y
'
' ' À
'
' '
'
'
' '
' construirlo '
'
' #
'
' '
' '
'
' ' '
'
' '
' '
'
Salir con
'
' '
% %Se localizó
'
' excepción
'
'
'
'
'
'
'
' $
'
' ' Leer número de campos en tamanhos[0]
'
' '
' $
'Leer descriptor &
Leer de '
& Leer tamaño ' &
archivo de campos '
' de campo Leer de disco tamanhos[i]
'
' '
%  '%
binario '
' tamanhos[0] en binario
'
'
'
'
'
'
'
' $
'
' '
'
' 'Leer a nombre tamanhos[1] bytes
'
'
' '
'
'
' '
' Leer a cuenta tamanhos[2] bytes
'
' '
&
'
' Procesar registro Leer a carrera tamanhos[3] bytes
'
' 
'
' mientras haya ' Leer a clave tamanhos[4] bytes
'
' '
'
'
' '
'
'
' '
'Agregar a la lista el registro
'
' '
%
'
' construido con estos datos
'
'
'
'
'
'
'
% Cerrar el archivo

Comparemos ahora el algoritmo con el código del listado 10.7. La parte que
corresponde a abrir el archivo y localizarlo se encuentra en las lı́neas 380 a 389.
En esta opción sı́ procesamos por separado la excepción que nos pueda dar la
localización del archivo binario del que el usuario solicita leer porque es posible que
no exista dicho archivo. Por eso, en lugar de la tradicional excepción IOException,
acá tratamos de atrapar una excepción que nos indica que no se encontró el
archivo. Igual que en el caso anterior, sin embargo, salimos con una excepción del
método, pues tenemos que regresar a solicitar otra opción.
De manera similar a como lo hicimos para escribir, construimos un flujo de la
clase DataInputStream y le pasamos como argumento un objeto de la clase FileIn-
509 Entrada y salida

putStream, que es subclase de InputStream, esto último lo que pide el constructor.


Una vez abierto el flujo – si llegamos a la lı́nea 390 – procedemos a adquirir la
descripción de los campos en el archivo binario.
En la lı́nea 391 leemos el número de campos de cada registro almacenado en
el archivo en los primeros dos bytes del mismo, usando para ello formato de short;
y en la lı́nea 392 construimos un arreglo para almacenar ahı́ los tamaños de cada
campo. En las lı́neas 393 a 395 leemos con máscara de short cada uno de los
tamaños y los almacenamos en el arreglo de shorts tamanhos.
Una vez que tenemos esto, procedemos a leer del flujo arreglos de k bytes,
donde k está dado pı́e la posición correspondiente de tamanhos. Para poder hacer
esto en una iteración construimos un arreglo de bytes del máximo tamaño de los
campos – esto se obtiene en las lı́neas 396 a 400 – utilizando para ello el método
read(byte[] b, int off, int len) que me permite ir leyendo pedazos de len bytes. Como
en el caso de la lectura de cadenas, intentamos primero leer el primer campo, y
si esta lectura nos indica que no pudo leer damos por terminado el flujo – lı́nea
401 –. En cambio, si pudo leer al primer campo, procedemos a leer tantos campos
como nos indique tamanhos[0], en “automático”, esto es, sin la garantı́a de que se
encuentren en el flujo – lı́neas 403 a 417 – dejando en manos del mecanismo de
excepciones si hay algún problema. Para no tener que llamar a cada campo por su
nombre, simplemente usamos un switch – lı́neas 404 a 417 que convierte al arreglo
de bytes en una cadena, que es lo que espera la clase Estudiante.
A continuación, en las lı́neas 422 y 423 procedemos a agregar a la base de
datos el registro completo recién leı́do, construyendo el registro al vuelo.
Si hay algún error de entrada/salida, la excepción correspondiente se atrapa
y se sale del método, mandando un mensaje alusivo y repitiendo la excepción.
En esta opción presentamos una cláusula finally, ya que queremos que, aunque
haya un error a la mitad, el flujo se cierre para liberar recursos. Esto se hace en
las lı́neas 429 a 435; sin embargo, tenemos que poner el enunciado que cierra el
archivo en un bloque try pues puede lanzar una excepción. Si sucede esto último,
simplemente absorbemos la excepción, ya que no importa que haya pasado se va
a salir del método.

Avance del flujo sin leer


Como en el archivo que estamos construyendo tenemos tamaño fijo de los
registros, sabemos en qué byte empieza, digamos, el i-ésimo registro. Todo lo que
tenemos que hacer es leer el número de campos y el tamaño de cada campo para
calcular el tamaño del registro, y desde ese punto saltar i  1 registros de tamaño k,
donde k es el tamaño de registro calculado. El único problema que tenemos acá es
que el acceso al flujo sigue siendo secuencial, ası́ que los bytes que saltemos en
10.9 Escritura y lectura de campos que no son cadenas 510

la lectura ya no los podemos regresar3 . n el listado 10.8 agregamos una opción al


menú para poder acceder al i-ésimo registro, siempre y cuando se haga al principio
del proceso. Esto pudiéramos usarlo para descartar un cierto número de registros
y leer únicamente a partir de cierto punto.

Código 10.8 Salto de bytes en lectura secuencial MenuListaIOReg (1/3)

481 case LEEREGISTROK :


482 // P e d i r e l nombre d e l f l u j o
483 try {
484 s A r c h i v o = pideNombreArch ( cons , LEER ) ;
485 a r c h i v o R e g s I n = new D a t a I n p u t S t r e a m
486 ( new F i l e I n p u t S t r e a m ( s A r c h i v o ) ) ;
487 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
488 System . e r r . p r i n t l n ( "el archivo de entrada "
489 + ( s A r c h i v o != n u l l ? s A r c h i v o : "nulo" )
490 + " no existe " ) ;
491 throw e ;
492 } // end o f t r y  c a t c h
493 // C a l c u l a r e l tamanho d e l r e g i s t r o
494 i n t tamR = 0 ;
495 try {
496 s h o r t tam = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
497 tamanhos = new s h o r t [ tam + 1 ] ;
498 tamanhos [ 0 ] = tam ;
499 tamR = 0 ;
500 f o r ( i n t i = 1 ; i <= tam ; i ++) {
501 tamanhos [ i ] = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
502 tamR += tamanhos [ i ] ;
503 } // end o f f o r ( i n t i = 1 ; i <= tam ; i ++)
504 } catch ( I O E x c e p t i o n e ) {
505 System . e r r . p r i n t l n ( "No pude leer parámetros " ) ;
506 r e t u r n LEEREGISTROK ;
507 } // end o f c a t c h
508 // C a l c u l a r e l número t o t a l de r e g i s t r o s en e l f l u j o
509 int f i l e S i z e = 0;
510 try {
511 f i l e S i z e = a r c h i v o R e g s I n . a v a i l a b l e ( ) / tamR ;
512
513 } catch ( I O E x c e p t i o n e ) {
514 System . e r r . p r i n t l n ( " Error al calcular Núm bytes " ) ;
515 } // end o f t r y  c a t c h
516 // P e d i r e l r e g i s t r o s o l i c i t a d o que e s t e en r a n g o s
517 i n t numR = 0 ;
511 Entrada y salida

Código 10.8 Salto de bytes en lectura secuencial MenuListaIOReg (2/3)

518 try {
519 System . o u t . p r i n t ( " Ahora dime el numero de registro (0.."
520 + ( f i l e S i z e  1 ) + ") -->" ) ;
521 subcad = cons . r e a d L i n e ( ) ;
522 numR = 0 ;
523 f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++) {
524 numR = numR∗10 + s u b c a d . c h a r A t ( i )  ’0’ ;
525 } // end o f f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++)
526 i f ( numR < 0 | | numR >= f i l e S i z e ) {
527 System . o u t . p r i n t l n ( "Hay menos de " + numR
528 + ". Del 0 al "
529 + ( fileSize  1));
530 r e t u r n LEEREGISTROK ;
531 }
532 } catch ( I O E x c e p t i o n e ) {
533 System . o u t . p r i n t l n ( " Error al leer n’umero de registro " ) ;
534 } // end o f t r y  c a t c h
535 // S a l t a r e l numero de b y t e s r e q u e r i d o s p a r a u b i c a r s e
536 // en e l r e g i s t r o s o l i c i t a d o .
537 i n t a S a l t a r = (numR  1 ) ∗ tamR ;
538 i n t pude = 0 ;
539 try {
540 // S a l t a r b y t e s
541 pude = a r c h i v o R e g s I n . s k i p B y t e s ( a S a l t a r ) ;
542 // S i e s que hubo l o s s u f i c i e n t e s
543 i f ( pude >= a S a l t a r ) {
544 bCadena = new byte [ tamR ] ;
545 // Leemos e l r e g i s t r o s o l i c i t a d o .
546 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) ;
547 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
548 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
549 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
550 switch ( i ) {
551 case 2 :
552 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
553 break ;
554 case 3 :
555 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
556 break ;
557 case 4 :
558 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
559 break ;
560 default :
561 break ;
562 } // end o f s w i t c h ( i )
563 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
10.9 Escritura y lectura de campos que no son cadenas 512

Código 10.8 Salto de bytes en lectura secuencial MenuListaIOReg (3/3)

554 // Se arma un o b j e t o de l a c l a s e E s t u d i a n t e
555 E s t u d i a n t e nuevo = new E s t u d i a n t e ( nombre , c u e n t a ,
556 carrera , clave );
557 System . o u t . p r i n t l n ( nuevo . d a R e g i s t r o ( ) ) ;
558 } // end o f i f ( pude >= a S a l t a r )
559 } catch ( I O E x c e p t i o n e ) {
560 System . o u t . p r i n t l n ( "Hubo error " ) ;
561 } // end o f t r y  c a t c h
562 r e t u r n LEEREGISTROK ;

10.9.3. Acceso semi directo a archivos binarios

Un archivo binario tiene un tamaño fijo de registros; en muchas ocasiones los


registros, o la información propiamente dicha, viene precedida de un descriptor
de los registros – lo que antes llamamos el encabezado del archivo – pero esta
información aparece una única vez y siempre al principio del archivo. Los flujos
que hemos visto hasta ahora, a’un los binarios, son secuenciales, esto es, para leer
el registro i hay que leer previamente los i  1 registros que lo preceden, o en
todo caso saltar los bytes correspondientes a i  1 registros. Debemos notar que
incluso con los saltos (skip), éstos son siempre hacia el final del archivo: lo que no
podemos hacer es querer leer el registro i y posteriormente el registro j con j   i.4
Sin embargo, es muy común querer un acceso directo5 . Por ejemplo, suponga-
mos que tenemos una base de datos ordenada alfabéticamente y queremos listar
en orden inverso. Con los flujos esto equivaldrı́a a cerrar y abrir el archivo por
cada registro. Lo ideal es que sin costo adicional, pudiéramos recorrer el archivo
de atrás hacia adelante. También se presenta esta necesidad en las bases de da-
tos, donde se guarda en disco una tabla con alguna llave (key) que identifica a
un registro, y a continuación la posición de ese registro en el archivo. Se podrı́a
hacer una búsqueda inteligente sobre la tabla (por ejemplo, búsqueda binaria) que
requiere la posibilidad de ir hacia adelante o hacia atrás en la lectura del archivo.
En Java tenemos una clase que nos da esta facilidad y que es la clase Ran-
4
Para poder hacer esto en archivos secuenciales tenemos que cerrar y volver a abrir el archivo
para que se vuelva a colocar al principio del mismo y poder saltar hacia el final del archivo. Otra
opción es que el archivo tenga implementado los métodos mark y reset.
5
Llamamos directo a lo que en inglés se conoce también como random – aleatorio –, porque
consideramos que aquél es un término más adecuado. Aleatorio tiene un sentido de no determinis-
mo y lo que se hace con este tipo de acceso es poder llegar directamente (no secuencialmente)
a un cierto registro.
513 Entrada y salida

domAccessFile. Hay que tener presente que aunque esta clase maneja archivos en
disco no hereda de ningún flujo (stream) o de lector/escritor (Reader/Writer), sino
que hereda directamente de Object, aunque sı́ se encuentra en el paquete java.io.
Los ejemplares de esta clase proveen tanto lectura como escritura en el mis-
mo objeto. en general podemos pensar en un archivo de acceso directo como un
arreglo en disco, donde cada elemento del arreglo es un registro y al cual quere-
mos tener acceso directamente a cualquiera de los registros sin seguir un orden
predeterminado.
Con un archivo de este tipo tenemos siempre asociado un apuntador de archivo
(en adelante simplemente apuntador), que se encarga de indicar en cada momento
a partir de donde se va a realizar la siguiente lectura/escritura. Si el apuntador
está al final del archivo y viene una orden de escritura, el archivo simplemente
se extiende (el arreglo crece); si estando al final del archivo viene una orden de
lectura, la máquina virtual lanzará una excepción EOFException. Si se intenta
realizar alguna operación después de que el archivo fue cerrado, se lanzará una
IOException. Se tiene un método que se encarga de mover al apuntador, lo que
consigue que la siguen te lectura/escritura se lleve a cabo a partir de la posición a
la que se movió el apuntador. Estas posiciones son absolutas en términos de bytes.
Este tipo de archivos se pueden usar para lectura, escritura o lectura/escritura,
dependiendo de qué se indique al construir los ejemplares.
Si bien no hereda de ninguna de las clases Stream, como ya dijimos, implemen-
ta a las interfaces DataOutput, DataInput y Closeable. Como se pueden imaginar,
las dos primeras también son implementadas por DataOutputStream y DataInputS-
tream, por lo que tendremos prácticamente los mismos métodos que ya conocemos
de estas dos últimas clases, todos en una única clase. Revisaremos únicamente
aquellos métodos que no conocemos todavı́a, dando por sentado que contamos
con los métodos para leer y escribir de DataInputStream y DataOutputStream.

public class RandomAccessFile implements DataOutput,


DataInput, Closeable
Constructores:
public RandomAccesFile(File file , String mode)
throws FileNotFoundException
Nuevamente tenemos que File representa a un archivo fı́sico, ya sea que
ya ha sido construido a través de un flujo o que se le dé la identificación
completa de un archivo en disco. La cadena mode representa el tipo de
uso que se le va a dar al archivo y puede ser:
10.9 Escritura y lectura de campos que no son cadenas 514

class RandomAccessFile (continúa. . . )


”r” Abre el archivo sólo para lectura. Si con un archivo abierto en
este modo se intenta escribir, se lanzará una IOException1‘.
”rw” El archivo se abre para lectura/escritura. Si el archivo no existe,
se intenta crear nuevo.
”rws” Igual que ”rw”, excepto que exige que todo cambio al archivo o
al descriptor (meta-datos) del archivo se refleje inmediatamente
en el dispositivo, que se haga sincronizado con el enunciado.
”rwd” Igual que ”rws”, excepto que los meta-datos no tienen que ac-
tualizarse de manera sincronizada.
Estos dos últimos modos son muy útiles para garantizar que si hay algún
problema con el sistema, todo lo que se escribió desde la aplicación en
efecto se vea reflejado en el dispositivo local. Si el dispositivo no es local,
no hay garantı́a de que esto suceda.
Las excepciones que pueden ser lanzadas (aunque no es necesario anun-
ciarlas a todas) son:

IllegalArgumentException Si el modo del constructor no es uno de los


especificados.
FileNotFoundException Si no se encontró el archivo solicitado y se in-
tentó abrir sólo para lectura.
SecurityException Si se intentó abrir un archivo para lectura o para lec-
tura/escritura que el manejador de la seguridad no permita leer, o
para lectura/escritura que el manejador de seguridad no permita
escribir.
public RandomAccesFile(String name, String mode)
throws FileNotFoundException
En este caso la primera cadena da una identificación en disco del vuelve
a ser el modo en que vamos a usar el archivo. Se comporta exactamente
igual que el primer constructor que mostramos.

Métodos adicionales a los de DataInput y DataOutput


En el caso de los archivos de acceso directo siempre existe el problema de
que se trate de leer más allá del fin de archivo. Por ello, prácticamente
todos los métodos que intentan leer más de un carácter incondicionalmente
llegando al final del archivo sin haber leı́do todos los bytes y que son
redefinidos lanzan la excepción EOFException.
515 Entrada y salida

class RandomAccessFile (continúa. . . )


Como ésta es una subclase de IOException no hay necesidad de modificar el
encabezado – que por otro lado no se puede – para que avise que también
puede lanzar una EOFException, que se refiere a tratar de leer más allá del
fin de archivo.

public long length () throws IOException


Regresa el tamaño del archivo en bytes.

void setLength(long newLength) throws IOException


Establece un nuevo tamaño para el archivo. Si el tamaño anterior era
mayor, se trunca el archivo al nuevo tamaño, perdiéndose lo que habı́a
más allá del nuevo tamaño. Si el tamaño anterior era menos, se reserva
espacio para agrandar el archivo; sin embargo hay que tener en cuenta que
el contenido del espacio agregado no estará definido. En el caso de que el
archivo sea truncado y que el filePointer esté más allá del nuevo final del
archivo, el apuntador se colocará al final del archivo.

public void seek(long pos) throws IOException


Coloca el apuntador del archivo (filePointer) en la posición dada por pos,
contado en bytes a partir del principio del archivo. Ésta posición puede
estar más allá del fin de archivo sin que se lance una excepción de fin de
archivo (EOFException), pero sin que cambie tampoco el tamaño del ar-
chivo. Sin embargo, si una vez colocado el apuntador más allá del final, se
lleva a cabo una escritura, esta acción si modificará el tamaño del archivo.
La excepción se lanza si pos   0 u ocurre un error de entrada/salida.
public long getFilePointer () throws IOException
Regresa la distancia al principio del archivo, en bytes, de la posición actual
del archivo (el próximo byte que va a ser leı́do o escrito).

Con esto ya tenemos las herramientas necesarias para acceder al disco con
acceso directo.

10.9.4. Lectura directa de registros

Como ya mencionamos, cualquier archivo en disco (o algún otro dispositivo de


almacenamiento en bytes) puede ser leı́do o escrito bajo cualquier tipo de flujo;
el tipo de flujo nos indica únicamente cómo interpretar los bytes: no describe el
10.9 Escritura y lectura de campos que no son cadenas 516

contenido del archivo.

Figura 10.15 Algoritmo para agregar registros desde archivo de acceso directo
$
'
' Pedir nombre de archivo
'
'
'
' Abrir archivo para#lectura
'
'
'
'
'
'
'
Leer número de tamanhos[0] Ð Primer short
'
'
'
' campos del archivo
'
'
'
'
'
' $ $
'
' ' '
' &Leer siguiente
'
'
' Calcular tamaño & Sumar tamanhos[i]
'
'  short
'
' de registro % i=1,. . . , tamanhos[0] '
' %
'
' Sumarlo
'
'
'
'
'
'
'
' $ $
'
' '
'
' 'Obtener del usuario el '
' 'Leer cadena de
'
'
' '
' &
'
' '
' número del registro consola
'
' '
'
'
' '
' '
' Procesar dı́gito
'
' '
' solicitado '
% 
'
Agregar registros ' '
' mientras haya
& '
'
'
'
desde archivo de '
'
'
' '
' $
acceso directo ' '
'
'
'
'
'
' Calcular posición en
'
&pos Ð
'
'
'
'
'
'
'
'
'
' bytes '
tamR numR 
'
'
'
'
'
'
'
'
%
|
encabezado |
' Procesar registro '
'
' &
Colocar el apuntador en esa posición
'
'
'
'
'
' (mientras se den) ''
'
' '
' $
'
' '
' '
'
' '
'
' 'Leer nombre
'
'
' ' &
'
' '
' Leer registro desde Leer cuenta
'
' '
'
'
' '
' disco '
' Leer carrera
'
' '
' '
%
'
' '
'
'
' '
' Leer clave
'
' '
'
'
' '
'
'
' '
' $
'
' '
' '
'
' '
' &Construir registro
'
' '
' Agregar a lista
'
' '
' nuevo
'
% '
% registro leı́do '
%
Agregar a lista
517 Entrada y salida

El único problema que enfrentamos es si suponemos un flujo en el que queramos


leer cadenas y no tenemos caracteres de fin de lı́nea en el archivo; en este caso
una única lectura nos darı́a todo el contenido del archivo, terminando la cadena
con un fin de archivo. En este sentido, sı́ hay que distinguir (desde la aplicación)
cuando estamos trabajando con un archivo de cadenas o de registros de tamaño
fijo.
La lectura directa nos permite “brincar” dentro del archivo, hacia adelante y
hacia atrás, buscando un byte en una posición determinada. Lo que quisiéramos
hacer con nuestra base de datos, guardada en un archivo en disco, es poder leer y
agregar en desorden a los registros que se encuentran en el disco. También debe
ser posible agregar únicamente a un subconjunto de estos registros. El algoritmo
para este caso se encuentra en la figura 10.15 en la página opuesta.
Podemos revisar cada uno de estos subprocesos en términos del diagrama. Por
ejemplo, pedir el nombre del archivo consiste de exactamente el mismo proceso
que en el caso del acceso al k-ésimo registro. La implementación se puede ver en
el listado 10.9. La parte correspondiente a la lectura y apertura del archivo tienen
que estar en un bloque try, ya que ambas operaciones pueden lanzar excepciones
– lı́neas 605 a 612. Lo primero que hacemos es solicitar del usuario el nombre del
archivo en disco que vamos a utilizar – lı́nea 606 – y a continuación lo tratamos
de abrir exclusivamente para lectura, lo que que hacemos poniendo como segundo
argumento del constructor "r" en la lı́nea 607.
Si se lanza la excepción de archivo no encontrado, simplemente emitimos un
mensaje acorde y salimos a mostrar nuevamente el menú – lı́neas 608 a 612 –,

mientras que si lo que tenemos es un error de entrada/salida realmente ya no


podemos hacer nada y exportamos la excepción – lı́neas 613 y 614.
Lo siguiente que queremos hacer es leer el encabezado del archivo para calcular
el tamaño del registro. También esto se lleva a cabo de la misma manera que lo
hicimos en la lectura de archivos binarios, por lo que ya no es necesaria una
explicación.

Código 10.9 Lectura del nombre del archivo y apertura del mismo case LEERDIRECTO (1/2)

603 case LEERDIRECTO :


604 // P e d i r e l nombre d e l f l u j o
605 try {
606 s A r c h i v o = pideNombreArch ( cons , o p c i o n ) ;
607 archivoRndm = new R a n d o m A c c e s s F i l e ( s A r c h i v o , "r" ) ;
608 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
10.9 Escritura y lectura de campos que no son cadenas 518

Código 10.9 Lectura del nombre del archivo y apertura del mismo case LEERDIRECTO (2/2)

609 System . e r r . p r i n t l n ( "el archivo de entrada "


610 + ( s A r c h i v o != n u l l ? s A r c h i v o : "nulo" )
611 + " no existe " ) ;
612 r e t u r n LEERDIRECTO ;
613 } catch ( I O E x c e p t i o n e ) {
614 throw e ;
615 } // end o f t r y  c a t c h
616 // C a l c u l a r e l tamaño d e l r e g i s t r o
617 tamR = 0 ;
618 numBytes = 0 ;
619 try {
620 s h o r t tam = 0 ;
621 tam = archivoRndm . r e a d S h o r t ( ) ;
622 numBytes = 2 ;
623 tamanhos = new s h o r t [ tam + 1 ] ;
624 tamanhos [ 0 ] = tam ;
625 tamR = 0 ;
626 f o r ( i n t i = 1 ; i <= tam ; i ++) {
627 tamanhos [ i ] = archivoRndm . r e a d S h o r t ( ) ;
628 numBytes+=2; tamR += tamanhos [ i ] ;
629 } // end o f f o r ( i n t i = 1 ; i <= tam ; i ++)
630 } catch ( I O E x c e p t i o n e ) {
631 System . e r r . p r i n t l n ( "No pude leer parámetros " ) ;
632 return opcion ;
633 } // end o f c a t c h
634 // C a l c u l a r e l número t o t a l de r e g i s t r o s en e l f l u j o
635 // p a r a d e t e c t a r p o s i c i o n e s e r r ó n e a s de r e g i s t r o s
636 f i l e S i z e = 0;
637 try {
638 f i l e S i z e = archivoRndm . l e n g t h ( ) / tamR ;
639 } catch ( I O E x c e p t i o n e ) {
640 System . e r r . p r i n t l n ( " Error al calcular Núm bytes " ) ;
641 } // end o f t r y  c a t c h

Una vez que calculamos el tamaño del encabezado (numBytes), el tamaño del
registro (tamR) y el número de registros lógicos en el archivo (fileSize) procedemos
a iterar, pidiéndole al usuario el número de registro, tantos como desee, del registro
que desea leer y agregar a la lista en memoria. Esto se lleva a cabo en las lı́neas
642 a 667 en el listado 10.10 en la página opuesta.
519 Entrada y salida

Código 10.10 Petición del número de registro al usuario case LEERDIRECTO

642 w h i l e (numR >= 0 ) {


643 // P e d i r e l r e g i s t r o s o l i c i t a d o que e s t e en r a n g o s
644 numR = 0 ;
645 try {
646 System . o u t . p r i n t ( " Ahora dime el número de registro "
647 + "(0.." + ( f i l e S i z e  1 )
648 + ", -1 para terminar )"
649 + " a agregar -->" ) ;
650 subcad = cons . r e a d L i n e ( ) ;
651 i f ( s u b c a d . c h a r A t ( 0 ) < ’0’ | | s u b c a d . c h a r A t ( 0 ) >= ’9’ ) {
652 numR =  1;
653 continue ;
654 } // end o f i f
655 numR = 0 ;
656 f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++) {
657 numR = numR∗10 + s u b c a d . c h a r A t ( i )  ’0’ ;
658 } // end o f f o r
659 i f ( numR < 0 | | numR >= f i l e S i z e ) {
660 System . o u t . p r i n t l n ( "Hay menos de " + numR
661 + ". Del 0 al "
662 + ( fileSize  1));
663 return opcion ;
664 }
665 } catch ( I O E x c e p t i o n e ) {
666 System . o u t . p r i n t l n ( " Error al leer número de registro " ) ;
667 } // end o f t r y  c a t c h

Elegimos leer del usuario una cadena, para evitar errores de lectura en caso
de que el usuario no proporcione un entero – lı́nea 650. Calculamos el entero
correspondiente usando la regla de Horner, que proporciona una manera sencilla,
de izquierda a derecha, de calcular un polinomio. Supongamos que tenemos un
polinomio
cn xn cn1 xn1 . . . c0
Podemos pensar en un número en base b como un polinomio donde x  b y
tenemos la restricción de que 0 ¤ ci   b. En este caso para saber el valor en base
10 evaluamos el polinomio. La manera fácil y costosa de hacerlo es calculando
cada una de las potencias de b para proceder después a multiplicar ci por bi .
¸
n
P px q  ci xi

i 0

Pero como acabamos de mencionar, esta es una manera costosa y poco elegante
10.9 Escritura y lectura de campos que no son cadenas 520

de hacerlo. La regla de Horner nos dice:

P px q  c 0 x pc 1 x pc 2 xp. . .qq . . .q

lo que nos permite evaluar el polinomio sin calcular previamente las potencias de
b. Por ejemplo, el número base 10 8725 lo podemos expresar como el polinomio

8  103 7  102 2  101 5  100  8000 700 20 5  8725

Si usamos la regla de Horner lo calculamos de la siguiente manera:

5 10p2 10p7 10p8qqq

Pero como tenemos que calcular de adentro hacia afuera, el orden de las opera-
ciones es el siguiente:

ppp8  10q 7q  10 2q  10 5

que resulta en la siguiente sucesión de operaciones:

8  10 80 7
87  10 870 2
872  10 8720 5
 8725
lo que permite leer los dı́gitos de izquierda a derecha e ir realizando las multipli-
caciones y sumas necesarias. La ventaja de esta regla es que cuando leemos el 8,
por ejemplo, no tenemos que saber la posición que ocupa, sino simplemente que
es el que está más a la izquierda. Dependiendo de cuántos dı́gitos se encuentren a
su derecha va a ser el número de veces que multipliquemos por 10, y por lo tanto
la potencia de 10 que le corresponde. Este algoritmo se encuentra codificado en
las lı́neas 655 a 658.
En las lı́neas 651 a 654 verificamos que el usuario no esté proporcionando un
número negativo (que empieza con ’-’). Si es ası́, damos por terminada la sucesión
de enteros para elegir registros.
Quisiéramos insistir en que no importa si el flujo es secuencial o de acceso
directo, una lectura se hace siempre a partir de la posición en la que se encuentra
el flujo. Si se acaba de abrir esta posición es la primera – la cero (0) –. Conforme se
hacen lecturas o escrituras el flujo o archivo va avanzando; en los flujos secuenciales
de entrada, mediante el método skip se puede avanzar sin usar los bytes saltados,
pero siempre hacia adelante. En cambio, en los archivos de acceso directo se cuenta
521 Entrada y salida

con el comando seek que es capaz de ubicar la siguiente lectura o escritura a partir
de la posición dada como argumento, colocando el apuntador de archivo en esa
posición.
En el listado 10.11 se encuentra el código que corresponde a la ubicación del
apuntador del archivo, frente al primer byte del registro solicitado por el usuario
en la iteración actual.

Código 10.11 Posicionamiento del apuntador del archivo y lectura case LEERDIRECTO

687 try {
688 // S a l t a r e l numero de b y t e s r e q u e r i d o s p a r a u b i c a r s e
689 // en e l r e g i s t r o s o l i c i t a d o .
690 a S a l t a r = numR∗tamR + numBytes ;
691 archivoRndm . s e e k ( a S a l t a r ) ;
692 bCadena = new byte [ tamR ] ;
693 // Leemos e l r e g i s t r o s o l i c i t a d o .
694 archivoRndm . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) ;
695 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
696 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
697 archivoRndm . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
698 switch ( i ) {
699 case 2 :
700 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
701 break ;
702 case 3 :
703 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
704 break ;
705 case 4 :
706 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
707 break ;
708 default :
709 break ;
710 } // end o f s w i t c h ( i )
711 } // end o f f o r ( i n t i = 1 ;
712 // Se arma un o b j e t o de l a c l a s e E s t u d i a n t e
713 E s t u d i a n t e nuevo = new E s t u d i a n t e
714 ( nombre , c u e n t a , c a r r e r a , c l a v e ) ;
715 i f ( miCurso == n u l l ) {
716 System . o u t . p r i n t l n ( "No existe Curso " ) ;
717 throw new N u l l P o i n t e r E x c e p t i o n ( " Uuups " ) ;
718 } // end o f i f ( miCurso == n u l l )
719 miCurso . a g r e g a E s t F i n a l
720 ( new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ) ;
721 } catch ( I O E x c e p t i o n e ) {
722 System . o u t . p r i n t l n ( "Hubo error en LEERDIRECTO " ) ;
723 } // end o f t r y  c a t c h
724 } // end w h i l e s e p i d a n r e g i s t r o s
10.9 Escritura y lectura de campos que no son cadenas 522

En la lı́nea 690 calculamos la posición que corresponde al entero dado en esta


iteración y en la lı́nea 691 colocamos al apuntador del archivo frente al siguiente
registro a leer. Como los registros están numerados del 0 en adelante, el número de
registros que se tienen que saltar son precisamente el entero proporcionado por el
usuario. Una vez colocados frente al registro deseado procedemos a leer el registro
desde disco – lı́neas 692 a 711 –. Una vez leı́do el registro procedemos a agregarlo
a la lista que se está construyendo – lı́neas 713 a 720 –. En este bloque hay varias
operaciones que pueden lanzar una excepción, por lo que se colocan en un bloque
try. En caso de que haya algún problema, simplemente descarta esta escritura y
procede a pedir el siguiente número de registro.

10.9.5. Escritura directa de registros

En un archivo de acceso directo podemos tener varias situaciones:


1. Queremos guardar en un archivo de acceso directo a los registros de la lista
en memoria.
2. Queremos agregar registros al final del archivo.
3. Deseamos sustituir un registro en el archivo por otro proporcionado por el
usuario.
Las primeras dos situaciones no guardan ninguna diferencia con la de estar
trabajando con archivos binarios; insistimos en que las opciones que vamos a
tener con un archivo en disco depende “del color del cristal con el que lo veamos”.
En general, si tenemos el encabezado del archivo y registros de tamaño uniforme,
podemos verlo como un archivo binario o como uno de acceso directo. Por esta
razón nos concentraremos en la tercera situación, aclarando que la posición que se
dé para escribir el registro puede estar más allá del fin de archivo, lo que resultará,
en este tipo de archivos, en que se extienda el archivo, dejando como indefinidos
los bytes que se encuentren entre el final anterior y el actual. El algoritmo para
esta opción se encuentra en la figura 10.16.
Por supuesto que en otras aplicaciones puede ser la misma aplicación la que
modifique un registro y lo reescriba. En el contexto de nuestra aplicación pensa-
remos que el usuario nos da la posición del registro y el registro a escribir en esa
posición – esto para poder seguir trabajando en este contexto. El algoritmo para
esta opción corre paralelo al que dimos para lectura de archivo de acceso directo,
excepto que además de pedir número de registro, la aplicación tiene que pedir el
registro a escribir. Por lo anterior, esta opción es una combinación de la opción
que agrega un registro y de la que lee de un archivo de acceso directo. El código
correspondiente se encuentra en al listado 10.12.
523 Entrada y salida

Figura 10.16 Algoritmo para sobrescribir registros en archivo de acceso directo


$
'
' Pedir nombre de archivo
'
'
'
' Abrir archivo para#lectura y escritura
'
'
'
'
'
'
'
Leer número tamanhos[0] Ð Primer short
'
'
'
' de campos del archivo
'
'
'
'
'
' $ $
'
'
'
' '
' '
&Leer siguiente
'
' '
& Sumar tamanhos[i] 
'
' Calcular tamaño short
'
' i=1,. . . , tamanhos[0] '
'
' ' %
'
' de registro '
'
Sumarlo
'
' %
'
'
'
' $ $
'
' '
'
' '
' '
' Leer de consola
'
' '
' '
&
'
' '
' Obtener la posición cadena
'
' '
'
'
' '
' del registro a escribir ' Procesar dı́gito
'
' '
' '
' 
'
' '
' %
'
' '
' mientras haya
Modificar registros '
& '
'
'
'
en archivo de '
' $
' '
'
acceso directo '
' '
' '
' Pedir nombre
'
' '
' '
&
'
' '
' Obtener del usuario Pedir cuenta
'
' '
'
'
' '
' el registro a escribir ' Pedir carrera
'
' '
' '
'
'
' '
' %
' ' Pedir clave
'
'
'
'
Procesar registro &
'
'
' (mientras se den) ' $
'
'
'
'
'
'
'
'
'
' Calcular posición
'
&pos Ð
'
'
'
'
'
'
'
'
'
' en bytes '

tamR numR
'
'
'
'
'
'
'
'
'
%
|encabezado |
'
' '
'
' '
' Colocar el apuntador
'
' '
'
'
' '
' en esa posición
'
' '
' $
'
' '
'
'
' '
' 'Escribir nombre
'
'
' '
' '
&
'
' '
' Escribir campos Escribir cuenta
'
' '
'
'
' '
' '
'
' '
' en el disco '
'
Escribir carrera
'
% '
% %
Escribir clave
10.9 Escritura y lectura de campos que no son cadenas 524

Código 10.12 Opción de modificar registros case GUARDARDIRECTO (1/2)

742 case GUARDARDIRECTO :


743 E s t u d i a n t e nuevo = n u l l ;
744 // P e d i r e l nombre d e l f l u j o
745 try {
746 s A r c h i v o = pideNombreArch ( cons , o p c i o n ) ;
747 archivoRndm = new R a n d o m A c c e s s F i l e
748 ( s A r c h i v o , "rw" ) ;
749 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
750 System . e r r . p r i n t l n ( "el archivo de entrada "
751 + ( s A r c h i v o != n u l l ? s A r c h i v o : "nulo" )
752 + " no existe " ) ;
753 return opcion ;
754 } catch ( I O E x c e p t i o n e ) {
755 throw e ;
756 } // end o f t r y  c a t c h
757 // C a l c u l a r e l tamaño d e l r e g i s t r o
758 tamR = 0 ;
759 numBytes = 0 ;
760 try {
761 s h o r t tam = 0 ;
762 tam = archivoRndm . r e a d S h o r t ( ) ;
763 numBytes = 2 ;
764 tamanhos = new s h o r t [ tam + 1 ] ;
765 tamanhos [ 0 ] = tam ;
766 tamR = 0 ;
767 f o r ( i n t i = 1 ; i <= tam ; i ++) {
768 tamanhos [ i ] = archivoRndm . r e a d S h o r t ( ) ;
769 numBytes+=2;
770 tamR += tamanhos [ i ] ;
771 } // end o f f o r ( i n t i = 1 ; i <= tam ; i ++)
772 } catch ( I O E x c e p t i o n e ) {
773 System . e r r . p r i n t l n ( "No pude leer parámetros " ) ;
774 return opcion ;
775 } // end o f c a t c h
776 // C a l c u l a r e l número t o t a l de r e g i s t r o s en e l f l u j o
777 f i l e S i z e = 0;
778 try {
779 f i l e S i z e = archivoRndm . l e n g t h ( ) / tamR ;
780 } catch ( I O E x c e p t i o n e ) {
781 System . e r r . p r i n t l n ( " Error al calcular Num bytes " ) ;
782 } // end o f t r y  c a t c h ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
783 w h i l e (numR >= 0 ) {
784 // P e d i r e l r e g i s t r o s o l i c i t a d o que e s t e en r a n g o s
785 try {
525 Entrada y salida

Código 10.12 Opción de modificar registros case GUARDARDIRECTO (1/2)

786 System . o u t . p r i n t ( " Ahora dime el numero de registro (0.."


787 + ", -1 para terminar )"
788 + " a escribir -->" ) ;
789 subcad = cons . r e a d L i n e ( ) ;
790 char c u a l = s u b c a d . c h a r A t ( 0 ) ;
791 i f ( c u a l < ’0’ | | c u a l >= ’9’ ) {
792 numR =  1;
793 continue ;
794 } // end o f i f
795 numR = 0 ;
796 f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++)
797 numR = numR∗10 + s u b c a d . c h a r A t ( i )  ’0’ ;
798 } catch ( I O E x c e p t i o n e ) {
799 System . o u t . p r i n t l n ( " Error al leer número de registro " ) ;
800 } // end o f t r y  c a t c h
801 // P e d i r l o s campos a e s c r i b i r
802 try {
803 nombre = pideNombre ( c o n s ) ;
804 cuenta = pideCuenta ( cons ) ;
805 c a r r e r a = p i d e C a r r e r a ( cons ) ;
806 c l a v e = pideClave ( cons ) ;
807 nuevo = new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ;
808 } catch ( I O E x c e p t i o n e ) {
809 System . o u t . p r i n t l n ( " Error al leer datos del "
810 + " estudiante .\ nNo se pudo escribir " ) ;
811 } // end o f t r y  c a t c h
812 try {
813 // S a l t a r e l numero de b y t e s r e q u e r i d o s p a r a u b i c a r s e
814 // en e l r e g i s t r o s o l i c i t a d o .
815 a S a l t a r = numR∗tamR + numBytes ;
816 archivoRndm . s e e k ( a S a l t a r ) ;
817 // E s c r i b o e l r e g i s t r o c o n s t r u i d o en nuevo
818 f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++) {
819 nombre = ( nuevo . daCampo ( i ) + b l a n c o s ) .
820 s u b s t r i n g ( 0 , tamanhos [ i ] ) ;
821 archivoRndm . w r i t e B y t e s ( nombre ) ;
822 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
823 } catch ( I O E x c e p t i o n e ) {
824 System . o u t . p r i n t l n ( "Hubo error en GUARDARDIRECTO " ) ;
825 } // end o f t r y  c a t c h
826 } // end w h i l e s e p i d a n r e g i s t r o s
827 try {
828 archivoRndm . c l o s e ( ) ;
829 } catch ( I O E x c e p t i o n e ) {
830 System . e r r . p r i n t l n ( "No pude cerrar archivo directo " ) ;
831 } // end o f t r y  c a t c h
832 r e t u r n GUARDARDIRECTO ;
10.10 Lectura y escritura de objetos 526

Realmente ya no hay mucho que explicar en esta opción, pues prácticamente


todo lo interesante ya se vio y los comentarios del código explican el orden. Sin
embargo es necesario insistir en un punto. Supongamos que leemos un registro (en
modo directo), lo modificamos y lo reescribimos; esto quiere decir que leemos y
escribimos de la misma posición. Por lo tanto, tendremos que colocar el apuntador
del archivo en la posición correspondiente; leemos el registro; lo modificamos;
volvemos a colocar el apuntador de archivo en la posición anterior; y escribimos.
Si no “regresamos” al apuntador a la posición donde empieza el registro, vamos
a sobreescribir en el siguiente registro, no en el que leı́mos. Esto se debe a que
cada lectura (o escritura) avanza al apuntador tantos bytes como se hayan leı́do
(o escrito); al final de la lectura el apuntador va a estar posicionado un byte más
allá del final del registro leı́do, por lo que es necesario regresarlo a que apunte al
principio del registro leı́do.

10.10 Lectura y escritura de objetos

Siendo Java un lenguaje orientado a objetos, uno se hace la pregunta de por


qué se usan mecanismos “anticuados” – como la transformación de y a bytes –
para lectura y escritura del ingrediente fundamental del lenguaje. Una respuesta,
aunque no del todo precisa, es que el almacenamiento en dispositivos externos si-
gue siendo todavı́a en términos de bytes. Pero no es del todo precisa esta respuesta
pues lo que intentamos con el uso de un lenguaje orientado a objetos es elevar
el nivel de abstracción de nuestras aplicaciones; esto es, independientemente de
cuál sea la representación subyacente de la información – el lenguaje de máquina
que corresponde a la representación de la información – deseamos, al nivel de la
aplicación, pensar en que tenemos almacenados objetos, independientemente de
cómo estén codificados estos objetos.
La caracterı́stica que buscamos escribiendo y leyendo objetos es el autoconoci-
miento que tienen de sı́ mismos los objetos: buscamos que tanto en el dispositivo
externo como en la memoria durante la ejecución el objeto pueda describirse a
sı́ mismo; que la lectura, escritura, y por lo tanto la interpretación, no dependa de
la aplicación, sino que dependa de lo que está almacenado (o se esté almacenan-
do). En resumen, deseamos que los objetos se almacenen con una descripción de
sı́ mismos; y que esta descripción sea manejada automáticamente por el lenguaje,
descargando de esta tarea a la aplicación.
Para ello cuenta Java con dos flujos, uno de entrada y otro de salida, que
contempla la lectura y escritura de objetos, ObjectInputStream y ObjectOutput-
527 Entrada y salida

Stream respectivamente. Las lecturas y escrituras se llevan a cabo como acabamos


de mencionar, por lo que los archivos presentan un tamaño mayor al que espe-
rarı́amos. También hay que tomar en cuenta que este modo de entrada/salida no
es muy eficiente, sobre todo si los objetos son grandes o tenemos una cantidad
grande de los mismos. En este último caso se recomienda “vaciar” el objeto como
lo hicimos antes y dejar que sea la aplicación la que lo interprete. Se conoce como
serialización a la conversión que se realiza de un objeto a un flujo de bytes y
deserialización la acción inversa: construir el estado de un objeto a partir de su
imagen en bytes.
Un aspecto muy importante e interesante es que al serializar un objeto estamos
guardando su estado, que es lo que lo distingue de otros objetos de la misma clase.
Al deserializarlo recuperamos precisamente el estado del objeto en el momento en
que fue serializado. De esta manera se puede recuperar fácilmente el estado de
una aplicación utilizando estos mecanismos. El contexto principal en el que estos
mecanismos se requieren es cuando se ejecuta una aplicación distribuida en varias
plataformas o de manera remota.
Regresando a los mecanismos de Java para leer/escribir objetos, llama la aten-
ción que ObjectInputStream y ObjectOutputStream no heredan de FilexxxputStream
sino que heredan directamente de xxxputStream, donde xxx corresponde a “in” o
“out”. Pero veamos parte de la definición de estas clases y los métodos que nos
aportan.
No es trivial guardar el estado de un objeto, pues algunos de los campos pueden
depender de la máquina virtual en la que se está ejecutando o del contexto. Por
ejemplo, si tenemos una lista ligada con objetos, la referencia al siguiente elemento
de la lista depende de la secuencia de ejecución y de la localidad asignada por
la máquina virtual: no es probable que en otra ejecución, posiblemente hasta
en otra máquina o con distinto sistema operativo, la lista quede armada en las
mismas posiciones que las dadas por el estado de los objetos. Otro ejemplo es
el de un objeto que guarda la posición de un cierto flujo donde se encuentra el
apuntador del archivo en el momento en que quedó definido su estado: en una
ejecución posterior o distinta puede suceder que el archivo ya haya cambiado y
esa posición no corresponda; o que al momento de leer el objeto el archivo ni
siquiera esté disponible. Por lo tanto, hay campos en un objeto serializado que
no tiene sentido guardar o leer porque se encuentran fuera de contexto. A este
tipo de campos los marcamos con el calificativo de transient para indicarle a la
aplicación que no los incluya en la serialización del objeto.
Otros campos que no vale la pena tampoco guardar son los campos estáticos
(static) de la clase. También en este caso, dado que esos campos pueden ser mo-
dificados por cualquiera de los objetos de esa clase, el valor que tienen cuando se
serializa al objeto no tiene por que ser el mismo que cuando se le deserializa. Por
10.10 Lectura y escritura de objetos 528

lo tanto, tampoco los campos estáticos van a ser incluidos en la serialización de


un objeto.
Si tenemos una clase que contiene campos que corresponden a otra(s) clase(s),
los objetos de la primera van a poder ser serializados si y sólo si cada una de
los campos que corresponden a clases contenidos en el objeto son también seria-
lizables. Asimismo, si una superclase no es serializable, ninguna subclase de ella
puede ser serializable, ya que si serializamos a la subclase le damos la vuelta a la
seguridad de la superclase que no desea ser serializada.
Cada objeto en el flujo consiste de un bloque de datos, en el que primero se
encuentra la descripción de los datos y después los datos propiamente dichos.
Como se puede ver de estos breves comentarios, la serialización y deserializa-
ción de objetos tiene muchı́simos puntos finos que hay que observar. Haremos una
lista más precisa de estos puntos hacia el final de esta sección. Pasemos a revisar
los flujos de objetos.

public class ObjectInputStream extends InputStream


implements ObjectInput, ObjectStreamConstants
Un flujo de entrada de objetos que deserializa datos primitivas que
fueron previamente escritos usando un flujo de salida de objetos
(ObjectOutputStream). Sólo los objetos que implementan la interfaz Se-
rializable son sujetos a ser serializados.
La interfaz ObjectInput hereda de la interfaz DataInput todos los métodos
que interpretan valores primitivas y agrega métodos para leer objetos y
bytes “en crudo”, sin interpretación.
La interfaz ObjectStreamConstants simplemente proporciona constantes
simbólicas para evitar el uso de números mágicos. Además de implementar
los métodos de DataInput (que no listaremos) provee métodos especı́ficos
para trabajar con objetos.
Clase anidada:
public abstract static class ObjectInputStream.GetField
Provee acceso para los campos persistentes leı́dos del flujo de entrada.
Campos:
Los heredados de ObjectStreamConstants.
Constructores:
protected ObjectInputStream() throws IOException,
SecurityException
529 Entrada y salida

La excepción de I/O es la usual. La excepción relativa a la seguridad tiene


que ver con el administrador de seguridad, si es que se violan los permisos
dados para la serialización.
public ObjectInputStream(InputStream in) throws IOException
Crea un ObjectInputStream que se asocia al flujo dado por in.
Métodos: (además de los de DataInput)
public void defaultReadObject() throws IOException,
ClassNotFoundException
Lee los datos no-estáticos y no-transeúntes de la clase asociada a este
flujo. Sólo puede ser invocada desde el método readObject. Si el flujo no
está activo, lanza la excepción NotActiveException. Si no encuentra en
su entorno la clase a la que pertenecen los objetos serializados, lanza la
excepción ClassNotFoundException.
protected boolean enableResolveObject( boolean enable)
Habilita al flujo para permitir que los objetos que son leı́dos del flujo pue-
dan ser reemplazados. Regresa el valor anterior antes de esta invocación.
Lanza la excepción si el manejador de seguridad no permite que se rees-
criba en este flujo.
public Object readObject()
throws IOException, ClassNotFoundException
Permite leer un objeto de un flujo serializado.
Además de las excepciones mencionadas en el encabezado, puede lanzar
una de las siguientes excepciones:

InvalidClassException Lanza esta excepción cuando la clase descrita en


la serialización no coincide con la que se encuentra declarada, no
reconoce el tipo de alguno de los campos o bien no cuenta con el
constructor requerido.

StreamCorruptedException La descripción del objeto viola la consisten-


cia del sistema y no lo puede leer.

OptionalDataException En el flujo no hay objetos, sino datos primitivos;


no está la descripción del objeto.
protected ObjectStreamClass readClassDescriptor ()
10.10 Lectura y escritura de objetos 530

throws IOException, ClassNotFoundException


Lee un descriptor de clase del flujo serializado. Se utiliza cuando la apli-
cación espera un descriptor de clase como siguiente elemento en el flujo.
protected Object resolveObject(Object obj)
throws IOException
Permite a subclases en las que se confı́a sustituir a un objeto por otro
durante la deserialización. Este método se llama después de invocar a
readObject pero antes de regresar de esta última invocación.

La lista que acabamos de dar dista mucho de ser exhaustiva, pero contiene la
suficiente información para poder cumplir con nuestro objetivo, que consiste en
serializar y deserializar la base de datos. Terminemos de reunir las herramientas
que requerimos mostrando la definición de la clase ObjectOutputStream.

public class ObjectOutputStream extends OutputStream


implements ObjectOutput, ObjectStreamConstants
Escribe al objeto serializado en un flujo de bytes. Únicamente los objetos
de clases que declaran ser Serializable pueden ser serializadas. Tanto la
superclase como todos los campos de la clase que a su vez sean objetos
deben ser serializables.
Clase anidada:
public abstract static class ObjectOutputStream.PutField
Aporta acceso de programación a los campos persistentes a escribirse e la
salida de objetos (ObjectOutput).
Campos:
Los heredados de ObjectStreamConstants.
Constructores:
public ObjectOutputStream(OutputStream out)
throws IOException
Crea un flujo ObjectOutputStream que escribe sobre el flujo de salida es-
pecificado (out). Escribe el encabezado de la serialización en el flujo de
salida. Además de la excepción IOException puede lanzar una excepción
de seguridad (SecurityException) si hubiese clases que pasen por encima
de las medidas de seguridad de acceso; y finalmente también puede lanzar
una excepción NullPointerException si out es nulo.
531 Entrada y salida

protected ObjectOutputStream() throws IOException,


SecurityException
Sirve para construir subclases que redefinan el flujo para no tener que
asignar espacio para datos privados que se usan únicamente en esta imple-
mentación de ObjectOutputStream. Realiza las verificaciones de seguridad
necesarias.
protected void annotateClass(Class<?> cl) throws IOException
Permite escribir en el flujo datos que corresponden a la clase. Corresponde
al método resolveClass de ObjectInputStream. Lo escrito por este método
debe ser leı́do por resolveClass.
public void defaultWriteObject () throws IOException
Escribe los campos no estáticos y no transeúntes al flujo. Sólo puede ser
invocado desde writeObject. So no se hace ası́ lanza una excepción del tipo
NotActiveException.
protected void drain() throws IOException
Vacı́a cualquier información acumulada en el flujo que no ha sido escrita
en el dispositivo. Similar a flush pero no se encadena con el dispositivo.
protected boolean enableReplaceObject (boolean enable)
throws SecurityException
Cuando es verdadera, permite el reemplazo de objetos en el flujo. Si
está habilitada, se invoca al métodoreplaceObject cada vez que se serializa
un objeto.
public ObjectOutputStream.PutField putFields ()
throws IOException
Obtiene el objeto que se usa para almacenar campos persistentes que van a
ser escritos en el flujo. Los campos se escriben cuando se invoca al método
writeFields.
protected Object replaceObject(Object obj)
throws IOException
Permite que clases confiables sustituyan un objeto por otro durante la
serialización. El objeto que reemplaza (obj) puede no ser serializable. El
objeto debe ser de la clase que está descrita en el flujo, o de una subclase
de ésta. De otra manera lanza una excepción.
public void reset () throws IOException
Ignora el estado de los objetos que ya fueron escritos en el flujo. El estado
del flujo se establece igual al de haberlo creado (new ObjectInputStream).
10.10 Lectura y escritura de objetos 532

public void write ( args ) throws IOException


La especificación de args puede ser cualquiera de las especificadas en
las interfaces DataOutput. Los argumentos son, como en otros flujos, un
arreglo de bytes (con, opcionalmente, la primera posición y el tamaño) o
un valor entero.
Además de aquellos métodos que escriben enteros, bytes, flotantes, etc., tenemos
métodos que provienen de la interfaz ObjectOutput. Únicamente mencionaremos
los que provienen de la segunda interfaz, ya que el resto funciona exactamente de
la misma manera que en todos los flujos que implementa DataOutput y que ya
revisamos.
public void writeFields () throws IOException
Escribe los campos que se encuentren en el buffer al flujo de objetos.
protected void writeStreamHeader() throws IOException
Se proporciona para que las subclases puedan pegar su propio encabezado
al flujo.
protected void writeClassDescriptor ( ObjectStreamClass desc)
throws IOException
Se utiliza para que las subclases puedan determinar la manera como van
a codificar la descripción de la clase.
public final void writeObject(Object obj) throws IOException
Escribe el objeto indicado en el flujo, incluyendo a la clase y la firma de
la clase a la que pertenece el objeto, los valores de las variables de estado
que no sean transeúntes y todos los supertipos de la clase. Se usa para
brincar la serialización y hacerla más ad-hoc para el objeto dado. Si se
reimplementa este método se tiene que hacer lo mismo con readObject de
ObjectInputStream. Las excepciones que puede lanzar son:

InvalidClassException Algo está mal con la clase utilizada por la serializa-


ción.
NotSerializableException Alguno de los objetos que forman parte del ob-
jeto que se desea escribir no implementa la interfaz Serializable.
IOException Excepciones lanzadas por el flujo subyacente.
protected void writeObject(Object obj) throws IOException
Lo usan las subclases para redefinir el método writeObject.
public void writeUnshared(Object obj) throws IOException
Escribe el objeto como único, sin referencias hacia objetos anteriores en
el mismo flujo.
533 Entrada y salida

Como se puede ver de la descripción de los métodos (y nos faltaron algunos)


la escritura y lectura en flujos de objetos es muy sofisticada, ya que permite
transferir objetos de una aplicación a otra, conservando cada uno su estado, de
manera transparente y muy efectiva. Cuando se están ejecutando aplicaciones
donde los datos (en este caso objetos) tiene que transitar por la red, tal vez el
tiempo adicional de escribir/leer en un flujo de objetos no es muy significativo. Sin
embargo, hay que tener presente que la lectura/escritura de objetos es un proceso
lento y que no debiera utilizarse en aplicaciones locales que manejen un número
grande de datos.
Regresemos a nuestro objetivo de esta sección que es la de escribir y leer a
flujos con formato de objetos. Nuevamente permitiremos en cualquier momento
de las ejecución de nuestra aplicación cargar o descargar objetos a y desde la base
de datos. Pero lo primero que tenemos que hacer es permitir que cada uno de los
objetos que pretendemos escribir implementen a la interfaz Serializable. Lo más
sencillo serı́a simplemente agregar este aspecto a nuestra clase Estudiante, y como
ésta extiende a InfoEstudiante, ambas tenemos que corregirlas para que implemen-
ten a la interfaz6 . Como InfoEstudiante originalmente implementa interfaces, a las
que no hay que ponerles el calificativo de ser serializables, con estas dos clases ya
cubrimos el requisito de que todas las superclases sean serializables. También hay
que notar que el registro consiste de cadenas y 7un referencia; como las cadenas
son serializables y la referencia también lo es, se cubre ampliamente el requisito
de ser serializable. Los cambios hechos a InfoEstudiante y Estudiante se pueden ver
en los listados 10.13 y 10.14 en la siguiente página respectivamente.

Código 10.13 Cambios a InfoEstudiante InfoEstudianteSerial (1/2)

1 import j a v a . i o . ∗ ;
2 /∗ ∗
3 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula
4 ∗ l a l i s t a de un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s
5 ∗ n o r m a l e s de una b a s e de d a t o s y f u n c i o n a m e d i a n t e un Menú
6 ∗/
7 c l a s s I n f o E s t u d i a n t e S e r i a l implements D a E s t u d i a n t e , S e r i a l i z a b l e {
......
16 /∗ ∗
17 ∗ C o n s t r u c t o r s i n p a r á m e t r o s
18 ∗/

6
Reescribimos estas dos clases agregando al nombre Serial para mantener intacto el trabajo
que realizamos con anterioridad.
10.10 Lectura y escritura de objetos 534

Código 10.13 Cambios a InfoEstudiante InfoEstudianteSerial (2/2)

19 public I n f o E s t u d i a n t e S e r i a l () {
20 nombre = c a r r e r a = c u e n t a = n u l l ;
21 }
22 /∗ ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
23 ∗ l o s campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas ,
24 ∗ m i e n t r a s que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́
25 ∗ p o r punto y coma .
26 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
27 ∗ cada uno de l o s campos que s e van a l l e n a r .
28 ∗ @ r e t u r n I n f o E s t u d i a n t e una r e f e r e n c i a a una l i s t a
29 ∗/
30 p u b l i c I n f o E s t u d i a n t e S e r i a l ( S t r i n g nmbre , S t r i n g c nt a ,
31 String crrera ) {
32 nombre = nmbre . t r i m ( ) ;
33 cuenta = cnta . trim ( ) ;
34 carrera = crre ra . trim ( ) ;
35 }
......

Como ya habı́amos mencionado, en la lı́nea 7 le cambiamos el nombre a la


clase y agregamos el que implementa la interfaz Serializable. Por este cambio de
nombre, también tuvimos que cambiar el nombre a los constructores – lı́neas 19
y 30 –. De similar manera tenemos que modificar a la clase Estudiante.

Código 10.14 Modificaciones a la clase Estudiante EstudianteSerial (1/2)

1 import j a v a . i o . ∗ ;
2
3 // i m p o r t i c c 1 . i n t e r f a z . C o n s o l a ;
4 /∗ ∗
5 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula l a l i s t a de un
6 ∗ c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s n o r m a l e s de una b a s e de
7 ∗ d a t o s y f u n c i o n a m e d i a n t e un Menú
8 ∗/
9 c l a s s E s t u d i a n t e S e r i a l extends I n f o E s t u d i a n t e S e r i a l
10 implements S e r i a l i z a b l e {
......
13 /∗ ∗ C o n s t r u c t o r s i n p a r á m e t r o s ∗/
14 public Estud i an teS er i al () {
15 super ( ) ;
16 clave = null ;
17 siguiente = null ;
18 s h o r t [ ] tam = { ( s h o r t ) ( tamanhos [ 0 ] + 1 ) ,
19 tamanhos [ 1 ] , tamanhos [ 2 ] , tamanhos [ 3 ] , 2 0 } ;
535 Entrada y salida

Código 10.14 Modificaciones a la clase Estudiante EstudianteSerial (2/2)

20 tamanhos = tam ;
21 }
22 /∗ ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
23 ∗ l o s campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas ,
24 ∗ m i e n t r a s que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́
25 ∗ p o r punto y coma .
26 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
27 ∗ cada uno de l o s campos que s e van a l l e n a r .
28 ∗ @ r e t u r n E s t u d i a n t e una r e f e r e n c i a a una l i s t a
29 ∗/
30 p u b l i c E s t u d i a n t e S e r i a l ( S t r i n g nmbre , S t r i n g cn ta ,
31 String crrera , String clve ) {
50 public Estud i an teS er i al daSiguiente () {
51 return s i g u i e n t e ;
52 }
53 /∗ ∗ A c t u a l i z a e l campo s i g u i e n t e .
54 ∗ @params E s t u d i a n t e l a r e f e r e n c i a que s e va a a s i g n a r .
55 ∗/
56 public void p o n S i g u i e n t e ( E s t u d i a n t e S e r i a l s i g ) {
57 siguiente = sig ;
58 }

......

En las lı́neas 9, 13, 30, 50 y 56 se encuentran las consecuencias de haber cam-


biado el nombre a ambas clases de las que hablamos. En la lı́nea 10 se encuentra
la declaración de que esta clase implementa a la interfaz Serializable, lo que la hace
susceptible de ser escrita o leı́da de un flujo de objetos.
Dado que la base de datos está compuesta por objetos de la clase Estudiante
y eso no lo queremos modificar, tenemos que dar métodos que conviertan de
EstudianteSerial a Estudiante y viceversa en la clase EstudianteSerial. Son métodos
sencillos que únicamente copian los campos. El código se puede ver en el listado
10.15.

Código 10.15 Conversión de Estudiante a EstudianteSerial y viceversa EstudianteSerial (1/2)

99 /∗ ∗
100 ∗ C o n v i e r t e a un e s t u d i a n t e en un e s t u d i a n t e s e r i a l , s i m p l e m e n t e
101 ∗ c o p i a n d o l o s campos c o r r e s p o n d i e n t e s .
102 ∗ @param nuevo e l E s t u d i a n t e .
103 ∗ @ r e t u r n s Un E s t u d i a n t e S e r i a l con e l mismo c o n t e n i d o que nuevo .
104 ∗/
10.10 Lectura y escritura de objetos 536

Código 10.15 Conversión de Estudiante a EstudianteSerial y viceversa EstudianteSerial (2/2)

105 p u b l i c v o i d d a E s t u d i a n t e S e r i a l ( E s t u d i a n t e nuevo ) {
106 nombre = nuevo . daNombre ( ) ;
107 c u e n t a = nuevo . daCuenta ( ) ;
108 c a r r e r a = nuevo . d a C a r r e r a ( ) ;
109 c l a v e = nuevo . d a C l a v e ( ) ;
110 }
111 /∗ ∗
112 ∗ C o n v i e r t e a un E s t u d i a n t e S e r i a l en un E s t u d i a n t e c o n s t r u y e n d o
113 ∗ uno de ’ e s t o s .
114 ∗ @param e l e s t u d i a n t e s e r i a l .
115 ∗ @ r e t u r n s un E s t u d i a n t e .
116 ∗/
117 public Estudiante daEstudiante () {
118 S t r i n g nmbre = t h i s . daNombre ( ) ;
119 S t r i n g c n t a = t h i s . daCuenta ( ) ;
120 String cr rera = this . daCarrera ( ) ;
121 S t r i n g c l v e = th is . daClave ( ) ;
122 r e t u r n new E s t u d i a n t e ( nmbre , c n ta , c r r e r a , c l v e ) ;
123 }

Habiendo hecho estos cambios, nos avocamos a la clase ListaCursoIO – a la


que identificaremos como ListaCursoIOObj – para ver los cambios necesarios en el
manejo de nuestra base de datos, que se localizan en la clase MenuListaIOObj.
Lo primero que tenemos que hacer es agregarle al método que pide el nombre
del archivo la opción para que pida un archivo del que se van a leer (o escribir)
objetos, lo que se encuentra en el listado 10.16.

Código 10.16 Solicitud de nombre de archivo para flujo de objetos MenuListaIOObj

182 p u b l i c S t r i n g pideNombreArch ( B u f f e r e d R e a d e r cons , i n t c a s o )


183 throws I O E x c e p t i o n {
184 S t r i n g m e n s a j e = "Por favor dame el nombre del archivo \n" ;
185 switch ( caso ) {

......
219 case LEEROBJETOS :
220 m e n s a j e += "de dónde vas a leer objetos " ;
221 break ;
222 case GUARDAROBJETOS :
223 m e n s a j e += "en dónde vas a escribir objetos " ;
224 break ;
......
537 Entrada y salida

Necesitamos también declarar un flujo de objetos, al principio del método


daMenu, lo que se encuentra en el listado 10.17.

Código 10.17 Declaración de flujo de objetos MenuListaIOObj

267 ObjectInputStream archvoObjetosIn = null ;


268 ObjectOutputStream archvoObjetosOut = n u l l ;

Con esto ya estamos listos para llenar los casos que nos ocupan. El algoritmo
para la lectura de los registros se muestra en la figura 10.17 y como se puede ob-
servar es prácticamente idéntico al de leer registros de un archivo directo, excepto
que por el hecho de leer objetos la máquina virtual se encarga de interpretarlos.
Figura 10.17 Algoritmo para la lectura de objetos
$
'
' Abrir el archivo
'
' $
'
' '
& &Leer el registro actual
Cargar registros
Lectura de Objetos
'
' (mientras haya) '%
'
' Ingresarlo a la base de datos
'
'
%
Cerrar el archivo

En este diagrama no pusimos el manejo de excepciones porque no forman parte


de la lógica general del algoritmo. En la figura 10.18 mostramos el diagrama para
la escritura a flujos de objetos, que es sumamente similar al de la lectura.

Figura 10.18 Escritura a un flujo de objetos

$ #
'
' Abrir el archivo
'
' Inicio
'
' Colocarse al principio de la lista
'
'
'
'
'
'
'
& $
'
' Convertir al estudiante actual
Escritura de Objetos '
&
'
' Tomar registro en estudiante serial
'
'
'
' (mientras haya) ' Escribirlo al flujo de objetos
'
' '
'
'
' %
'
' Tomar como actual al siguiente
'
%
Cerrar el archivo

Antes de proceder con la codificación de estos esquemas debemos nuevamente


declarar los flujos correspondientes y una variable para almacenamiento temporal
10.10 Lectura y escritura de objetos 538

de objetos de la clase EstudianteSerial, ası́ como modificar el menú y el manejo de


las opciones. El código correspondiente a esto se encuentra en el listado 10.18.

Código 10.18 Caso de lectura de objetos (declaraciones) (MenuListaIOObj)


......
267 ObjectInputStream archvoObjetosIn = null ;
268 ObjectOutputStream archvoObjetosOut = n u l l ;
......
272 EstudianteSerial nuevoSrl = null ;
......
293 + "(F)\ tLeer objetos de flujo \n"
294 + "(G)\ tEscribir objetos en flujo \n"
......
308 o p c i o n = " 0123456789 ABCDEFGZ " . i n d e x O f ( s o p c i o n ) ;

En el listado 10.19 se encuentra el código que corresponde al manejo de estos


dos casos en el menú.

Código 10.19 Caso de lectura de objetos MenuListaIOObj (1/3)


......
966 case LEEROBJETOS :
967 try {
968 archvoObjetosIn = null ;
969 s A r c h i v o = pideNombreArch ( cons , LEEROBJETOS ) ;
970 a r c h v o O b j e t o s I n = new O b j e c t I n p u t S t r e a m
971 ( new F i l e I n p u t S t r e a m ( s A r c h i v o ) ) ;
972 i f ( a r c h v o O b j e t o s I n == n u l l ) {
973 System . o u t . p r i n t l n ( "el archivo NO quedó abierto " ) ;
974 } // end o f i f ( a r c h v o O b j e t o s I n == n u l l )
975 boolean yaNoHay = f a l s e ;
976 w h i l e ( ! yaNoHay ) {
977 nuevoSrl = null ;
978 /∗ Cuando s e ac ab a un a r c h i v o de o b j e t o s y s e
979 ∗ t r a t a de l e e r s i m p l e m e n t e l a n z a una e x c e p c i ó n
980 ∗/
981 try {
982 nuevoSrl =
983 ( Estu di an teS er i al ) archvoObjetosIn . readObject ( ) ;
539 Entrada y salida

Código 10.19 Caso de lectura de objetos MenuListaIOObj (2/3)

984 } catch ( I O E x c e p t i o n e ) {
985 yaNoHay = t r u e ;
986 } // end o f t r y  c a t c h
987 i f ( yaNoHay ) {
988 continue ;
989 } // end o f i f ( a r c h v o O b j e t o s I n . a v a i l a b l e > 0 )
990 nuevo = n u e v o S r l . d a E s t u d i a n t e ( ) ;
991 miCurso . a g r e g a E s t O r d e n ( nuevo ) ;
992 } // end o f w h i l e ( ( nombre = a r c h i v o I n . r e a d L i n e ( ) . . .
993 } // end o f t r y
994 catch ( C l a s s N o t F o u n d E x c e p t i o n e ) {
995 System . o u t . p r i n t l n ( "Se está leyendo el archivo equivocado " ) ;
996 } // end o f c a t c h
997 catch ( I n v a l i d C l a s s E x c e p t i o n e ) {
998 System . o u t . p r i n t l n ( "La clase no permite ser serializada " ) ;
999 } // end o f c a t c h
1000 catch ( S t r e a m C o r r u p t e d E x c e p t i o n e ) {
1001 System . o u t . p r i n t l n ( "La descripción del objeto "
1002 + "es inconsistente " ) ;
1003 } // end o f c a t c h
1004 catch ( O p t i o n a l D a t a E x c e p t i o n e ) {
1005 System . o u t . p r i n t l n ( "No se encontraron objetos sino "
1006 + " datos directos " ) ;
1007 } // end o f c a t c h
1008 catch ( I O E x c e p t i o n e ) {
1009 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
1010 } // end o f t r y  c a t c h
1011 catch ( E x c e p t i o n e ) {
1012 System . o u t . p r i n t l n ( "No alcanzaron los datos " ) ;
1013 } // end o f c a t c h
1014 finally {
1015 i f ( a r c h v o O b j e t o s I n != n u l l ) {
1016 try {
1017 archvoObjetosIn . close ( ) ;
1018 } catch ( I O E x c e p t i o n e ) {
1019 System . e r r . p r i n t l n ( "No pude cerrar el"
1020 + " archivo de lectura " ) ;
1021 } // end o f t r y  c a t c h
1022 } // end o f i f ( a r c h i v o I n != n u l l )
1023 } // end o f f i n a l l y
1024 r e t u r n LEEROBJETOS ;
1025
1026 case GUARDAROBJETOS :
1027 try {
1028 s A r c h i v o = pideNombreArch ( cons , GUARDAROBJETOS ) ;
1029 archvoObjetosOut = null ;
10.10 Lectura y escritura de objetos 540

Código 10.20 Caso de lectura de objetos MenuListaIOObj (3/3)

1030 a r c h v o O b j e t o s O u t = new O b j e c t O u t p u t S t r e a m
1031 ( new F i l e O u t p u t S t r e a m ( s A r c h i v o ) ) ;
1032 System . o u t . p r i n t l n ( "Abrı́ el archivo " ) ;
1033 i f ( a r c h v o O b j e t o s O u t == n u l l ) {
1034 System . o u t . p r i n t l n ( "el archivo NO está abierto !" ) ;
1035 } // end o f i f ( a r c h v o O b j e t o s O u t == n u l l )
1036 l i s t a = ( ( E s t u d i a n t e ) miCurso . d a L i s t a ( ) ) ;
1037 int contador = 0;
1038 w h i l e ( l i s t a != n u l l ) {
1039 n u e v o S r l = new E s t u d i a n t e S e r i a l ( ) ;
1040 nuevoSrl . daEstudianteSerial ( l i s t a ) ;
1041 archvoObjetosOut . writeObject ( nuevoSrl ) ;
1042 l i s t a = l i s t a . daSiguiente ();
1043 } // end o f w h i l e ( l i s t a != n u l l )
1044 } catch ( S e c u r i t y E x c e p t i o n e ) {
1045 System . o u t . p r i n t l n ( "Se violaron condiciones de seguridad " ) ;
1046 } // end o f c a t c h
1047 catch ( I O E x c e p t i o n e ) {
1048 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
1049 } // end o f t r y  c a t c h
1050 finally {
1051 try {
1052 i f ( a r c h v o O b j e t o s O u t != n u l l ) {
1053 archvoObjetosOut . c l o s e ( ) ;
1054 } // end o f i f ( a r c h i v o O u t != n u l l )
1055 } catch ( I O E x c e p t i o n e ) {
1056 System . e r r . p r i n t l n ( "No pude cerrar el archivo " ) ;
1057 } // end o f t r y  c a t c h
1058 } // end o f f i n a l l y
1059 r e t u r n GUARDAROBJETOS ;

Como hasta ahora, la lectura es un poco más compleja que la escritura, ya


que sabemos lo que estamos escribiendo, pero al tratar de leer pueden venir mal
los datos, no existir el archivo, leer de más, etc.. Pasamos a explicar únicamente
aquellas lı́neas de código que no sean del todo claras.
Para abrir el archivo de entrada o de salida se construye un flujo de objetos
sobre un flujo de bytes – lı́neas 970–971 y 1030 – 1031 – ya que esa es la definición
del constructor de un flujo de objetos.
Se lee el registro a un EstudianteSerial – lı́nea 982 – 983 –, pero como la lista
que tenemos es de objetos de la clase Estudiante, lo tendremos que convertir antes
de agregarlo a la lista. En el caso de flujos de objetos no tenemos un indicador
adecuado de cuándo se acabó el archivo y lo único que podemos usar es la excep-
541 Entrada y salida

ción de entrada y salida que se va a alcanzar cuando ya no pueda leer del flujo.
Por ello, colocamos estas lı́neas en un bloque try–catch – 981 a 986 que atrape la
excepción y simplemente prenda una variable para avisar que ya no hay datos.
En las lı́neas 987 a 989 se detecta que ya se alcanzó el fin de archivo (o que no
se pudo leer) y se regresa al encabezado del while, para evitar tratar de procesar
datos erróneos.
Finalmente, en las lı́neas 990 y 991 se obtiene un objeto Estudiante a partir del
que se leyó y se agrega a la lista.
Las cláusulas catch externas a la lectura y que corresponden únicamente al
caso de lectura – lı́neas 994 a 1013 simplemente proporcionan un mensaje de error
de qué es lo que sucedió, lo mismo que las cláusulas catch del caso de escritura a
flujo de objetos – lı́neas 1044 a 1049 –.
En ambos casos que nos ocupan tenemos una cláusula finally que se encarga de
cerrar el archivo, preguntando antes si es que el archivo fue abierto adecuadamente
– lı́neas 1015 a 1022 en lectura y 1050 a 1058 en escritura – que además tiene
que estar, nuevamente, en un bloque try-catch para el caso en que se lance una
excepción.
Es interesante ver con un visor de texto en ASCII un archivo creado como
flujo de objetos ya que se ve, de alguna manera, la gran cantidad de información
que se encuentra adicional a los datos mismos. Esto se debe a que con cada
objeto se tiene que describir al mismo, lo que sucede ya que en un mismo flujo de
objetos podemos escribir objetos de distintas clases y al leerlos, automáticamente
se carga la descripción que trae consigo el objeto. Es por eso que la lectura siempre
es de un Object y se tiene que hacer un cast para obtener el objeto que creemos
estamos leyendo. Para “decodificar” un objeto de un flujo del que no sabemos a
qué clase pertenece, podemos utilizar la clase Class que proporciona toda clase de
herramientas para ello.

10.11 Colofón

Revisamos varias clases de flujos de entrada y salida, pero de ninguna manera


fuimos exhaustivos. Sin embargo, los conceptos vertidos en este capı́tulo deben
servir de plataforma para poder revisar (y utilizar adecuadamente) flujos de otras
clases que no hayan sido tratados. Una revisión completa de toda la entrada y
salida que ofrece Java está más allá del alcance de este material.

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