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 &