Documente Academic
Documente Profesional
Documente Cultură
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.
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.
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.
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!
EJemplo 1.4.2
El número 327.15 en el sistema decimal (o con base 10) se puede presentar de
la siguiente manera:
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
EJemplo 1.4.4
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
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
¸
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
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:
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:
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
Procesador
central
Unidad de control
Unidad aritmética
Unidad lógica
Cache
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
Lenguajes de programación
(4)
Programa
objeto
(binario)
(3)
Unidad
Datos para de control
el programa (5)
Programa a
ejecutar Resultados
Programa
(2) MEMORIA (6)
ensamblador
(binario) (1)
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.
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
“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
Caracteres
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
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 ;
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:
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.
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.
Números reales
Para representar a los número reales tenemos dos opciones, conocidas como
punto fijo y punto flotante.
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.
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.
1.32456E6 1324560
1.32456E 6 .00000132456
1.32456E3 1324.56
1.5 La arquitectura de von Neumann 26
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.
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
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.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?
Especificación
Análisis y diseño
Implementación
Validación
Mantenimiento
Refinamiento y extensión
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:
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.
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
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
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
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
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.
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
Esto se logra mediante reglas de acceso, que pueden ser de alguno de los tipos
que siguen:
Atributos
Mensajes (messages)
Métodos (methods)
Clases (classes)
Ejemplares (instances)
Herencia (inheritance)
Elementos geométricos
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–.
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.
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
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
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 :
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).
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.
En este caso queda muy claro que el orden en que están listados los platos
2.3 Diseño estructurado 54
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
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
$
&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
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
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ó.
ax by c
dx ey f
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
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.
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
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.
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
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.
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
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."
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
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
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
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
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
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
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
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 .
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
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 }
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
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 ( )
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.
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 )
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
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
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.
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
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
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.
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
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.
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 )
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
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:
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,
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 )
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 .
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
el paquete Reloj.
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.
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 ) ;
http://lambda.fciencias.unam.mx/icc1
3. Ejercicios 112
Ejercicios
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.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.13.- Tenemos el registro bibliográfico de un libro. Escribe una clase en Java que
lo represente.
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.
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
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
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
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.
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
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 + "\"" ) ;
}
}
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:
Memoria Heap
1: minusc está en minúsculas
ESTÁ EN MINÚSCULAS
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
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
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
Cadenas Para c o m p a r a r
c a d e n a s p a r a Comparar
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
/∗ 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
∗/
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
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
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
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.
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 ) ;
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 " ;
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 ;
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.
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
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 }
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 }
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
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.
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
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
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
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 }
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
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
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 }
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.
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.
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.
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.
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
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 }
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.
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
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
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.
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 }
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 }
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.
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.
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
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
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–.
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)
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 }
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.
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–.
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
Ejercicios
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
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.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.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
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
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:
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)
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.
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
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.
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
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
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
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
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)
Código 5.6 Métodos que actualizan y regresan un campo seleccionado Estudiante (2/2)
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.
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.
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
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
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
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
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)
Variables Heap
nombre cuenta carrera correo signte
(4086)
(32100) (40800)
201 (3198)
[]
yo (4086)
“elisa@ciencias”
(3198)
“067302682”
(40800)
“Elisa Viso”
(32100)
Variables Heap
nombre cuenta carrera correo signte
(4086)
(40800) (32100)
201 (3198)
[]
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”
(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).
(4086)
(40800) (32100)
201 (3198)
[]
yo (4086)
(3198)
“elisa@ciencias”
(32100) “067302682”
(40800) “Elisa Viso”
(75300)
(2500) (8200)
101 (5470)
[]
tu (75300)
(5470)
“periquito@unam”
(8200) “067302682”
(2500)
“Perico de los palotes”
5.2 La lista de registros 208
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
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
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
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.
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
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 }
acabamos de mostrar.
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
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 }
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
Antes:
@ lista @ ultimo
@ Inf o @
[]
nuevo
Después:
@ lista @ ultimo
@ Inf o @
nuevo
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 }
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 }
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.
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 }
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.
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
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
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
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 }
Para el caso en que se busca un registro que tenga una subcadena en un campo
231 Datos estructurados
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
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
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)
Variables Heap
(4086)
(40800) (32100)
127 (3198)
[]
(4086)
viejo (3198)
“lolita@ciencias”
(32100) “99999999”
(40800) “Lola”
(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”
(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)
(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
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 ;
}
...
2
Se acostumbra regresar un arreglo, pero como es un tema que todavı́a no revisamos, el
5.2 La lista de registros 240
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 @
@ []
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
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.
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 }
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 }
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
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
51600 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
51700 /∗ Prueba de l o s métodos ∗/
51800 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
51900 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
52000 C u r s o L i s t a s i c c 1 = new C u r s o L i s t a s ( "7004" ) ;
52100 // Agregamos a l g u n o s e s t u d i a n t e s a l p r i n c i p i o de l a l i s t a
52200 i c c 1 . agregaAlumno ( "Viso Elisa " , 1 2 2 , " 06730268 " ,
52300 " elisa@ciencias " ) ;
52400 i c c 1 . agregaAlumno ( " Magidin Arturo " , 1 2 2 , " 089428452 " ,
52500 " arturo@ams " ) ;
52600 i c c 1 . agregaAlumno ( " Torres Maria " , 1 2 7 , " 301245329 " ,
52700 " torresm@ciencias " ) ;
52800 i c c 1 . agregaAlumno ( " Barajas Miguel " , 1 0 6 , "000" , "no tiene " ) ;
52900 System . o u t . p r i n t l n ( i c c 1 . dameCurso ( ) ) ;
53000 System . o u t . p r i n t l n ( i c c 1 . d a m e L i s t a ( ) ) ;
53100 // Probamos a h o r a que a g r e g u e en o r d e n
53200 C u r s o L i s t a s i c c O r d = new C u r s o L i s t a s ( "7004" ) ;
53300 Estudiante laLista = icc1 . getLista ( ) ;
53400 E s t u d i a n t e nuevo ;
53500 C u r s o L i s t a s a l R e v e s = new C u r s o L i s t a s ( "7005" ) ;
53600 l a L i s t a = iccOrd . g e t L i s t a ( ) ;
53700 w h i l e ( l a L i s t a != n u l l ) {
53800 a l R e v e s . agregaAlumno ( l a L i s t a . getNombre ( ) ,
53900 laLista . getCarrera () ,
54000 l a L i s t a . getCuenta ( ) ,
54100 l a L i s t a . getCorreo ( ) ) ;
54200 laLista = laLista . getSiguiente ();
54300 }
54400 System . o u t . p r i n t l n ( "al reves :\n" +
54500 alReves . dameLista ( ) ) ;
54600 C u r s o L i s t a s a l F i n a l = new C u r s o L i s t a s ( "7006" ) ;
54700 a l F i n a l . agregaAlumno ( "Viso Elisa " , 1 2 2 , " 06730268 " ,
54800 " elisa@ciencias " ) ;
54900 a l F i n a l . agregaAlumno ( " Magidin Arturo " , 1 2 2 , " 089428452 " ,
55000 " arturo@ams " ) ;
55100 a l F i n a l . agregaAlumno ( " Torres Maria " , 1 2 7 , " 301245329 " ,
55200 " torresm@ciencias " ) ;
55300 a l F i n a l . agregaAlumno ( " Barajas Miguel " , 1 0 6 , "000" , "no tiene " ) ;
55400 System . o u t . p r i n t l n ( a l F i n a l . dameCurso ( ) ) ;
249 Datos estructurados
55500 /∗ E l i m i n a n d o r e g i s t r o s de l a l i s t a ∗/
55600 E s t u d i a n t e q u i e n = a l F i n a l . l o c a l i z a A l u m n o ( "Viso" ,NOMBRE) ;
55700 i f ( q u i e n != n u l l ) {
55800 a l F i n a l . eliminaAlumno ( quien ) ;
55900 }
56000 System . o u t . p r i n t l n ( " Después de eliminar a Viso :\n"
56100 + a l F i n a l . dameCurso ( ) ) ;
56200 q u i e n = a l F i n a l . l o c a l i z a A l u m n o ( " Barajas " ,NOMBRE) ;
56300 i f ( q u i e n != n u l l ) {
56400 a l F i n a l . eliminaAlumno ( quien ) ;
56500 }
56600 System . o u t . p r i n t l n ( " Después de eliminar a Barajas :\n"
56700 + a l F i n a l . dameCurso ( ) ) ;
56800 C u r s o L i s t a s enOrden = new C u r s o L i s t a s ( "7006" ) ;
56900 enOrden . agregaEnOrden ( new E s t u d i a n t e ( "Viso Elisa " ,
57000 " elisa@ciencias " ,
57100 " 06730268 " ,
57200 122));
57300 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Magidin Arturo " ,
57400 " arturo@ams " ,
57500 " 089428452 " ,
57600 122));
57700 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Torres Maria " ,
57800 " torresm@ciencias " ,
57900 " 301245329 " ,
58000 127));
58100 enOrden . agregaEnOrden ( new E s t u d i a n t e ( " Barajas Miguel " ,
58200 "no tiene " ,
58300 "000" ,
58400 106));
58500 System . o u t . p r i n t l n ( enOrden . dameCurso ( ) ) ;
58600 }
La salida que se produce con la ejecución del método main de la clase se muestra
en el listado 5.29.
Figura 5.29 Ejecución del método main de la clase CursoListas Salida (1/3)
Figura 5.30 Ejecución del método main de la clase CursoListas Salida (2/3)
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
089428452 M a t e má t i c a s Magidin Arturo
arturo@ams
Grupo : 7006 Núm . de alumnos : 4
251 Datos estructurados
Figura 5.30 Ejecución del método main de la clase CursoListas Salida (3/3)
301245329 C i e n c i a s de l a T i e r r a T o r r e s Marı́a
torresm@ciencias
006730268 M a t e má t i c a s Viso E l i s a
elisa@ciencias
Ayudándose de los enunciados que imprimen para localizar cada una de las
lı́neas que lo hacen, se puede seguir la ejecución de la clase de manera sencilla,
por lo que ya no ilustraremos esta ejecución.
La programación del menú para tener acceso a la base de datos del curso es
similar al caso en que los registros eran cadenas, excepto que ahora para agregar
a cualquier estudiante hay que construir un objeto de la clase Estudiante. El algo-
ritmo es el mismo, por lo que ya no lo mostramos. La programación se encuentra
en el listado 5.34 en la siguiente página.
Es en esta clase donde realmente se van a crear objetos nuevos para poderlos
ir enlazando en la lista. Por ejemplo, para agregar un estudiante, una vez que
tenemos los datos (lı́neas 6500-6800 en el listado 5.34 en la siguiente página),
procedemos a invocar al método agregaAlumno de la clase CursoListas. Este método
tiene como argumento un objeto, que es creado en el momento de invocar a agrega
– ver lı́nea 7000 del listado 5.34 en la siguiente página. También podemos pedir
que agregue a un estudiante nuevo en el lugar que le toca alfabéticamente, si a-
gregamos la opción de agregar en orden –lı́neas 7200 a 7800–. Estos son los únicos
métodos en los que se crean objetos, y esto tiene sentido, ya que se requiere crear
objetos sólo cuando se desea agregar a un estudiante.
Para el caso de los métodos de la clase CursoListas que regresan una referencia
a un objeto de la clase Estudiante, es para lo que se declaró la variable cual – lı́nea
5500 del listado 5.34 en la siguiente página. En estos casos el objeto ya existe, y
lo único que hay que pasar o recibir son las variables que contienen la referencia.
5.2 La lista de registros 252
19300 /∗ ∗
19400 ∗ Método <code>pideNombre </code >, p i d e e l nombre d e l e s t u d i a n t e .
19500 ∗ @param c o n s v a l o r de t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
19600 ∗ del usuario .
19700 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
19800 ∗/
19900 p r i v a t e S t r i n g pideNombre ( S c a n n e r c o n s ) {
20000 System . o u t . p r i n t l n ( "Dame el nombre :\t" ) ;
20100 S t r i n g nombre = c o n s . n e x t L i n e ( ) ;
20200 r e t u r n nombre ;
20300 }
20400
20500 /∗ ∗
20600 ∗ Método <code>p i d e C u e n t a </code >, p i d e e l número de c u e n t a d e l
20700 ∗ estudiante .
20800 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
20900 ∗ del usuario .
21000 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
21100 ∗/
21200 private S t r i n g pideCuenta ( Scanner cons ) {
21300 System . o u t . p r i n t l n ( "Dame el numero de cuenta :\t" ) ;
21400 S t r i n g cuenta = cons . nextLine ( ) ;
21500 return cuenta ;
21600 }
21700
21800 /∗ ∗
21900 ∗ Método <code>p i d e C o r r e o </code >, p i d e e l c o r r e o d e l e s t u d i a n t e .
22000 ∗ @param c o n s t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
22100 ∗ del usuario .
22200 ∗ @ r e t u r n t i p o <code>S t r i n g </code >, e l d a t o p r o p o r c i o n a d o .
22300 ∗/
22400 private S t r i n g pideCorreo ( Scanner cons ) {
22500 System . o u t . p r i n t l n ( "Dame el correo :\t" ) ;
22600 S t r i n g c o r r e o = cons . nextLine ( ) ;
22700 return correo ;
22800 }
22900
23000 /∗ ∗
23100 ∗ Metodo <code>p i d e C a r r e r a </code >, p i d e e l c o r r e o d e l e s t u d i a n t e .
23200 ∗ @param c o n s v a l o r de t i p o <code>S c a n n e r </code> p a r a l e e r d a t o s
23300 ∗ del usuario .
23400 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >, e l d a t o p r o p o r c i o n a d o .
23500 ∗/
23600 private i nt p i d e C a r r e r a ( Scanner cons ) {
23700 int carrera = 0;
257 Datos estructurados
1 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a C o n s u l t a s
2 L i s t a s / MenuCursoListas
3 E l i g e una o p c i ó n :
4 [ 0 ] Terminar
5 [ 1 ] Agregar e s t u d i a n t e
6 [ 2 ] Buscar e s t u d i a n t e
7 [ 3 ] L i s t a r todos
8 [ 4 ] L i s t a r l o s que c a z a n con . . .
9 [5] Eliminar estudiante
10 [ 6 ] Mostrar e s t u d i a n t e
11 [ 7 ] A g r e g a r en o r d e n
12
13 E l i g e una o p c i ó n >
14 7
15 Dame e l nombre :
16 Viso E l i s a
17 Dame e l numero de c u e n t a :
18 067302682
19 Dame e l c o r r e o :
20 elisa@ciencias
21 Las c a r r e r a s r e g i s t r a d a s s o n :
22 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
23 ===============================================
24 101 Actuaria
25 104 C i e n c i a s de l a Computación
26 106 Fı́ s i c a
27 122 M a t e má t i c a s
28 127 C i e n c i a s de l a T i e r r a
29 201 B i o l o gı́ a
30 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
31 =========================================================
32 Dame l a c a r r e r a :
33 104
34 E l i g e una o p c i ó n :
35 [ 0 ] Terminar
36 [ 1 ] Agregar e s t u d i a n t e
37 [ 2 ] Buscar e s t u d i a n t e
38 [ 3 ] L i s t a r todos
39 [ 4 ] L i s t a r l o s que c a z a n con . . .
40 [5] Eliminar estudiante
41 [ 6 ] Mostrar e s t u d i a n t e
42 [ 7 ] A g r e g a r en o r d e n
43
44 E l i g e una o p c i ó n >
45 3
259 Datos estructurados
91 [0] Terminar
92 [1] Agregar e s t u d i a n t e
93 [2] Buscar e s t u d i a n t e
94 [3] L i s t a r todos
95 [4] L i s t a r l o s que c a z a n con . . .
96 [5] Eliminar estudiante
97 [6] Mostrar e s t u d i a n t e
98 [7] A g r e g a r en o r d e n
99
100 E l i g e una o p c i ó n >
101 3
102
103 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
104 inia abrin@servidor
105 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
106 es Olaf aaguilar
107 098034011 Fı́ s i c a Cruz Cruz G i l Noé
108 ncruz
109 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
110 e Israel igarcia
111 099586197 B i o l o gı́ a Hubard E s c a l e r a A
112 lfredo ahubard
113 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
114 elio rtapia
115 067302682 C i e n c i a s de l a Computación Viso E l i s a
116 elisa@ciencias
117 Acciones a r e a l i z a r :
118 [ 0 ] Terminar
119 [ 1 ] Agregar e s t u d i a n t e
120 [ 2 ] Buscar e s t u d i a n t e
121 [ 3 ] L i s t a r todos
122 [ 4 ] L i s t a r l o s que c a z a n con . . .
123 [5] Eliminar estudiante
124 [ 6 ] Mostrar e s t u d i a n t e
125 [ 7 ] A g r e g a r en o r d e n
126
127 E l i g e una o p c i ó n >
128 1
129 Dame e l nombre :
130 Torres Morales Mauricio
131 Dame e l numero de c u e n t a :
132 081234567
133 Dame e l c o r r e o :
134 mauri@ciencias
261 Datos estructurados
135 Las c a r r e r a s r e g i s t r a d a s s o n :
136 C a t a l o g o de c a r r e r a s de l a F a c u l t a d de C i e n c i a s
137 ===============================================
138 101 Actuaria
139 104 C i e n c i a s de l a Computación
140 106 Fı́ s i c a
141 122 M a t e má t i c a s
142 127 C i e n c i a s de l a T i e r r a
143 201 B i o l o gı́ a
144 217 Manejo S u s t e n t a b l e de Zonas C o s t e r a
145 =========================================================
146 Dame l a c a r r e r a :
147 101
148 Acciones a r e a l i z a r :
149 [ 0 ] Terminar
150 [ 1 ] Agregar e s t u d i a n t e
151 [ 2 ] Buscar e s t u d i a n t e
152 [ 3 ] L i s t a r todos
153 [ 4 ] L i s t a r l o s que c a z a n con . . .
154 [5] Eliminar estudiante
155 [ 6 ] Mostrar e s t u d i a n t e
156 [ 7 ] A g r e g a r en o r d e n
157
158 E l i g e una o p c i ó n >
159 3
160
161 081234567 Actuaria T o r r e s M o r a l e s Ma
162 uricio mauri@ciencias
163 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
164 inia abrin@servidor
165 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
166 es Olaf aaguilar
167 098034011 Fı́ s i c a Cruz Cruz G i l Noé
168 ncruz
169 098159820 M a t e má t i c a s G a r cı́ a V i l l a f u e r t
170 e Israel igarcia
171 099586197 B i o l o gı́ a Hubard E s c a l e r a A
172 lfredo ahubard
173 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
174 elio rtapia
175 067302682 C i e n c i a s de l a Computación Viso E l i s a
176 elisa@ciencias
177 Acciones a r e a l i z a r :
178 [ 0 ] Terminar
5.2 La lista de registros 262
222 Acciones a r e a l i z a r :
223 [ 0 ] Terminar
224 [ 1 ] Agregar e s t u d i a n t e
225 [ 2 ] Buscar e s t u d i a n t e
226 [ 3 ] L i s t a r todos
227 [ 4 ] L i s t a r l o s que c a z a n con . . .
228 [5] Eliminar estudiante
229 [ 6 ] Mostrar e s t u d i a n t e
230 [ 7 ] A g r e g a r en o r d e n
231
232 E l i g e una o p c i ó n >
233 3
234
235 081234567 Actuaria T o r r e s M o r a l e s Ma
236 uricio mauri@ciencias
237 070245672 M a t e má t i c a s A b rı́ n B a t u l e V i r g
238 inia abrin@servidor
239 400001528 M a t e má t i c a s A g u i l a r S o lı́ s A r i
240 es Olaf aaguilar
241 098034011 Fı́ s i c a Cruz Cruz G i l Noé
242 ncruz
243 099586197 B i o l o gı́ a Hubard E s c a l e r a A
244 lfredo ahubard
245 098162075 Manejo S u s t e n t a b l e de Zonas C o s t e r a T a p i a Vázquez Rog
246 elio rtapia
247 067302682 M a t e má t i c a s Viso E l i s a
248 elisa@ciencias
249
250 Acciones a r e a l i z a r :
251 [ 0 ] Terminar
252 [ 1 ] Agregar e s t u d i a n t e
253 [ 2 ] Buscar e s t u d i a n t e
254 [ 3 ] L i s t a r todos
255 [ 4 ] L i s t a r l o s que c a z a n con . . .
256 [5] Eliminar estudiante
257 [ 6 ] Mostrar e s t u d i a n t e
258 [ 7 ] A g r e g a r en o r d e n
259
260 E l i g e una o p c i ó n >>
261 0
262 Mucho g u s t o en h a b e r l e s e r v i d o
263 Fue un p l a c e r s e r v i r t e
264 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
5.2 La lista de registros 264
Con esto damos por terminado este capı́tulo. Se dejan como ejercicios las si-
guientes mejoras:
En el primer caso lo que hay que cambiar es la condición de parada del ciclo.
Para el segundo caso se trata de cambiar las condiciones de búsqueda de lugar
cuando se agrega en orden. Por último, como podemos tener varios alumnos en
la misma carrera, queremos que estén ordenados alfabéticamente dentro de cada
carrera.
265 Datos estructurados
Ejercicios
5.1.- Para el siguiente relato, describe cuáles métodos deben estar en la interfaz
para la clase:
5.2.- Queremos una clase que calcule la trayectoria balı́stica de armas de distintos
tipos –ver http://en.wikipedia.org/wiki/External ballistics. Diseña una interfaz
que calcule trayectorias balı́sticas para al menos cuatro armas distintas.
Decide si las cuatro armas requieren o no de los mismos parámetros, y si no
es ası́, diseña métodos para cada tipo de arma.
5.3.- Haz el código en Java para el algoritmo que se encuentra descrito en el
siguiente diagrama de Warnier. Se trata de un método que recibe como
parámetro de dónde leer y regresa como resultado la suma. Se le proporcio-
narán números al método y se marcará el fin de los datos con un Ctl-D, que
indica el fin de datos.
$ $
'
' &Iniciar acumulador
'
'
' Inicio
'
'
' %Pedir primer número
'
'
'
'
'
' $ $
'
'
' '
' &suma Ð suma + cuadrado(número)
'
&Suma número '
' '
'
'
'
Suma de &Pudo leer ' %Leer el siguiente número
cuadrados '
' (mientras no
'
'
' se acabe ''
'
'
'
'
la entrada) '
'
'
'
` !
'
' '
%
'
'
'
Pudo leer H
'
'
' !
'
%Final Entregar suma
Nota: Deberá codificarse un método en Java.
5.4.- Tenemos la clase CursoListas como se construyó en este capı́tulo. Suponga-
mos que queremos a los estudiantes inscritos en carreras del área de Fı́sico-
Matemáticas (claves cien). Dado el siguiente algoritmo (en un diagrama de
5. Ejercicios 266
$ !
'
'
' Inicio Obtener nuevo número de cuenta
'
'
'
'
'
'
'
& #
Pedir viejo número de cuenta
Cambia cuenta Proceso
'
'
' Cambia el número de cuenta
'
'
'
'
'
' !
'
%Final Regresa el viejo número de cuenta
5.6.- Como en el caso anterior, tenemos un curso registrado con los nombres
ordenados alfabéticamente por nombre del estudiante. Queremos cambiar el
nombre y, si es necesario, reubicar el registro. El algoritmo se encuentra en el
diagrama de Warnier que sigue. Codifica el algoritmo a un método en Java,
267 Datos estructurados
5.8.- Tenemos una lista de enteros cuyos métodos básicos de acceso a sus atributos
se dan en el listado que sigue.
5. Ejercicios 268
package C o n s u l t a s L i s t a s ;
import j a v a . u t i l . S c a n n e r ;
Agrega el método main y construye a pie una lista que tenga los números
(5,2,0,7,10,3,4). La forma de construir esta lista se muestra en el diagrama
de Warnier a continuación.
$ $
'
'
' & Declarar cabeza
'
'
' inicio con número 5
'
'
' %
'
'
' actual Ð cabeza
'
'
'
'
& $
Construir lista '
&Construir nuevo con num
'
'
'
Agregar num
actual.sigue Ð nuevo
'
'
'
(num=2,0,7,10,3,4) '
%
'
'
' actual Ð nuevo
'
'
'
'
'
' !
'
%Final H
Cabe aclarar que no se puede hacer esto en una iteración, pero podemos
usar nuevo para ir apuntando al elemento que se acaba de agregar.
5.9.- Escribe un método en esta clase que liste a todos los elementos de una lista.
269 Datos estructurados
5.10.- Haz un método ingrese a un objeto de la clase Enteros en orden a una lista
cualquiera.
5.11.- Haz un método que tome una lista cualquiera de Enteros y elemento por
elemento lo clone y construya una lista ordenada con ellos. Clonar un objeto
quiere decir construir uno nuevo con los mismos valores. La lista a la que
se clona debe permanecer igual. El algoritmo se muestra en el diagrama de
Warnier que se encuentra a continuación. El método recibe como parámetro
la lista a clonar y regresa la lista clonada ordenada.
$ $ !
'
'
' '
' lista vacı́a ListaClonada Ð null
'
' '
'
'
'
'
' '
' ` $
'
' &
'
'
' '
'
'
Inicio
' '
&Clonar primer elemento
'
' '
'
'
'
'
' '
' lista vacı́a Agregarlo a ListaClonada
'
' '
% '
'
%
'
'
'
'
actual Ð segundo de ListaVieja
'
'
&
Clonar y ordenar $
ListaVieja ' '
'
' Clonar el elemento actual
' '
' '
'
' Procesa elemento '
en ListaVieja
'
' &
'
'
' de ListaVieja Agregarlo en orden
'
'
' (mientras haya) '' a ListaClonada
'
' '
'
'
'
'
' '
% Avanzar al siguiente
'
'
' elemento en ListaVieja
'
'
'
'
'
' !
'
%Final Regresa ListaClonada
5.13.- Haz un diagrama de Warnier que encuentre el número mayor en una lista
de Enteros.
5.14.- Haz un diagrama de Warnier que encuentre el número menor en una lista
de Enteros.
5.15.- Haz un diagrama de Warnier para, dados dos enteros, el algoritmo reco-
rra una lista de Enteros y sustituya las presencias del primer entero por el
segundo entero.
5.16.- Diseña una clase que tenga objetos que puedan pertenecer a una lista, con
un campo que pueda almacenar un entero y con dos referencias, una de ellas
al objeto que sigue y otra al que le precede.
5.17.- Dada una lista de elementos como los descritos en el ejercicio anterior,
describe el algoritmo (en un diagrama de Warnier) para dar de alta a un
entero en una lista ordenada –se deben actualizar ambas referencias en cada
inserción a la lista–.
que, por el hecho de extender a la superclase, tiene al menos todos los atributos
y métodos de la superclase. Antes de entrar de lleno al concepto de herencia,
revisaremos a una clase de objetos muy común en las ciencias de la computación
que son los arreglos, estructuras de datos lineales, homogéneas, estáticas y de
acceso directo. Los necesitamos para modificar un poco a la superclase en lo que
respecta a las variables estáticas que están formadas por cadenas.
6.2 Arreglos
Hemos tratado los nombres que corresponden a las carreras como una cadena,
pero en la clase para el catálogo de carreras están implementados en arreglos.
Tenemos una lista con los nombres de las carreras y una lista “paralela” con las
claves de las carreras. El nombre que corresponde a la clave en la posición i de
la lista de claves es el que se encuentra en la posición i en la lista de nombres.
Organizamos estas listas colocando a cada nombre de carrera en una posición, y
en otro arreglo, en la posición correspondiente, colocamos su clave. Es conveniente
tener ordenadas las claves pues facilita –como verán más adelante– localizar una
clave determinada.
Si queremos registrar las calificaciones de las tareas entregadas por un estu-
diante durante el semestre, sabemos que vamos a dejar el mismo número de tareas
y que éste queda fijo al inicio del semestre (a lo más, una por semana, o sea 16).
Podemos pensar en las calificaciones de cada estudiante como un vector,
donde estamos suponiendo que cada estudiante tiene lugar para, a lo más, 16
calificaciones (del 0 al 15). Se ve, asimismo, muy útil el poder manejar a cada una
de las calificaciones refiriéndonos a su subı́ndice, en lugar de declarar calif0, calif1,
etc., imitando el manejo que damos a los vectores en matemáticas.
273 Herencia
Es importante notar que lo que tenemos es una colección de datos del mismo
tipo que se distinguen uno de otro por el lugar que ocupan dentro de la estructura.
A estas colecciones les llamamos en programación arreglos. La declaración de
arreglos tiene la siguiente sintaxis:
Sintaxis:
xdeclaración de arregloy ::= xtipoy[ ] xidentificadory;
Semántica:
Se declara una variable que va a ser una referencia a un arreglo de objetos
o datos primitivos del tipo dado.
Sintaxis:
xdeclaración de arreglos con inicializacióny ::=
xtipoy[ ] xidentificadory = { xlistay };
Semántica:
Para inicializar el arreglo se dan, entre llaves, valores del tipo del arre-
glo, separados entre sı́ por coma. El arreglo tendrá tantos elementos como
aparezcan en la lista, y cada elemento estará creado de acuerdo a la lista.
En el caso de un arreglo de objetos, la lista deberá contener objetos que ya
existen, o la creación de nuevos mediante el operador new.
6.2 Arreglos 274
Arreglo de objetos del tipo Estudiante con tres elementos, el primero y tercero
con el constructor por omisión y el segundo con los datos que se indican.
E s t u d i a n t e paco = new E s t u d i a n t e ( "Paco" ,
" 095376383 " , "Compu" , 7 ) ;
E s t u d i a n t e B a s i c o [ ] e s t u d i a n t e s = {new E s t u d i a n t e ( ) ,
paco ,
new E s t u d i a n t e ( )
};
Agregamos la declaración de los nombres de las carreras (String[]) y de las
claves de las mismas (int[]). Podemos ver los dos arreglos como paralelos:
al número de clave en la posición i le corresponde el nombre en la misma
posición.
static final String [ ] sCarreras = {
"Código invalido " , // lugar 0
" Actuarı́a " , // lugar 1
" Ciencias de la Computación " , // lugar 2
"Fı́sica" , // lugar 3
" Matemáticas " , // lugar 4
" Ciencias de la Tierra" , // lugar 5
" Biologı́a " , // lugar 6
"Manejo Sustentable de Zonas Costeras " } ; // lugar 7
275 Herencia
Y para las claves de las carreras hacemos un arreglo “paralelo” que contenga
las claves correspondientes:
static final int [ ] carreras = {
0, // l u g a r 0
101 , // l u g a r 1
104 , // l u g a r 2
106 , // l u g a r 3
122 , // l u g a r 4
127 , // l u g a r 5
201 , // l u g a r 6
217}; // l u g a r 7
Veamos en las figuras 6.1 a 6.3 a continuación cómo se crea el espacio para los
arreglos que se mostraron arriba. En los esquemas marcamos las localidades de
memoria que contienen una referencia con una “@”en la esquina superior izquier-
da. Esto quiere decir que su contenido es una dirección en el heap. Las localidades
están identificadas con rectángulos. El número que se encuentra ya sea inme-
diatamente a la izquierda o encima del rectángulo corresponde a lo que serı́a la
dirección de memoria en el heap (Java nunca maneja directamente direcciones de
memoria).
Variables Heap
1000 1004 1008 1012 1016
@ 1000 2 3 5 7 11
primos [0] [1] [2] [3] [4]
Variables Heap
2124 2130 2136
@ 2124 3.14 8.7 19.0
vector [0] [1] [2]
6.2 Arreglos 276
Variables Heap
3826
@ “Si”
@ 2784 3826 [0]
2784 @ 4218
2790 4218 [1]
cadenas “No”
E s t u d i a n t e [ ] e s t u d i a n t e s = { new E s t u d i a n t e ( ) ,
new E s t u d i a n t e ( "Paco" , " 095376383 " , c l a v e P a c o , 7 ) ,
new E s t u d i a n t e ( ) } ;
“Paco”
3200
“095376383”
4100
Sintaxis:
xdeclaración de un arreglo con tamaño dadoy ::=
xtipoy xidentificadory = new xtipoy[xexpresión enteray];
Semántica:
Se inicializa la referencia a un arreglo de referencias en el caso de objetos,
o de datos en el caso de tipos primitivos.
6.2 Arreglos 278
Si los ejemplos que dimos antes los hubiéramos hecho sin la inicialización serı́an:
1 i n t [ ] p r i m o s = new i n t [ 5 ] ;
2 f l o a t [ ] v e c t o r = new f l o a t [ 3 ] ;
3 E s t u d i a n t e B a s i c o [ ] e s t u d i a n t e s = new E s t u d i a n t e B a s i c o [ 3 ] ;
4 S t r i n g [ ] c a d e n a s = new S t r i n g [ 2 ] ;
5 S t r i n g [ ] s C a r r e r a s = new S t r i n g [ 8 ] ;
6 i n t [ ] c l a v e s = new i n t [ 8 ] ;
Variables Heap
1000 1004 1008 1012 1016
@ 1000 0 0 0 0 0
primos [0] [1] [2] [3] [4]
Variables Heap
2124 2130 2136
@ 2124 0.0 0.0 0.0
vector [0] [1] [2]
279 Herencia
Variables Heap
@ 1020 @
null [0]
1020
estudiantes @
1026 null [1]
@
1032 null [2]
Variables Heap
@
@ 2784 null [0]
2784 @
2790 null [1]
cadenas
Variables Heap
[0] [1] [2] [3] [4] [5] [6] [7]
@ 1000 @null @null @null @null @null @null @null @null
Para las claves de las carreras, podrı́amos tener la declaración sin inicialización
que se muestra en la figura 6.12.
6.2 Arreglos 280
i n t [ ] c l a v e s = new i n t [ 8 ] ;
Variables Heap
1000 1004 1008 1012 1016 1020 1024 1028
@ 1000 0 0 0 0 0 0 0 0
claves [0] [1] [2] [3] [4] [5] [6] [7]
Sintaxis:
xselección de un elemento de un arregloy ::=
xid. de arregloy [ xexpresión enteray ]
Semántica:
El operador r s es el de mayor precedencia de entre los operadores de Ja-
va. Eso indica que evaluará la expresión dentro de ella antes que cualquier
otra operación (en la ausencia de paréntesis). Una vez obtenido el entero
correspondiente a la xexpresión enteray –que pudiera ser una constante en-
tera– procederá a elegir al elemento en el arreglo con ese ı́ndice. El resultado
de esta operación es del tipo de los elementos del arreglo. De no existir el
elemento al que corresponde el ı́ndice calculado, el programa abortará con
el mensaje ArrayIndexOutOfBoundsException. Al primer elemento del arre-
glo le corresponde siempre el ı́ndice 0 (cero) y al último elemento el ı́ndice
n 1, donde el arreglo se creó con n elementos.
1 int [ ] enteros ;
2 ...
3 e n t e r o s = new i n t [ 5 ] ;
4 int i = 0;
5 while ( i < 5) {
6 e n t e r o s [ i ] = ( i + 1) ∗ 2 ;
7 i ++;
8 }
9 e n t e r o s = new i n t [ 7 ] ;
Variables Heap
p1q
@ null
enteros
Variables Heap
p3q [0] [1] [2] [3] [4]
@
0 0 0 0 0
1260 1260 1264 1268 1272 1276
enteros
Variables Heap
p8q [0] [1] [2] [3] [4]
@
2 4 6 8 10
1260 1260 1264 1268 1272 1276
enteros
Variables Heap
p9q [0] [1] [2] [3] [4]
@
2 4 6 8 10
2580 1260 1264 1268 1272 1276
enteros
[0] [1] [2] [3] [4] [5] [6]
0 0 0 0 0 0 0
2580 2584 2588 2592 2596 2600 2604
Hay una pequeña diferencia entre las dos versiones. En el caso del for, la
variable i es local a él mientras que en el while tuvimos que declararla fuera del
bloque. En ambos casos, sin embargo, esto se hace una única vez, que es el paso
de inicialización. En el caso del for no se puede hacer referencia a la variable i
fuera del bloque, pues no existe fuera de él, mientras que en el caso del while la
variable i existe fuera del bloque. No hay manera de usar una variable que no ha
sido declarada en la expresión booleana del while.
Otra manera de hacer esto con un for, usando dos variables enumerativas, una
para i y otra para i + 1, pudiera ser como sigue:
1 int [ ] enteros ;
2 ...
3 e n t e r o s = new i n t [ 5 ] ;
4 f o r ( i n t i = 0 , j = 1 ; i < 5 ; i ++, j ++) {
5 enteros [ i ] = j ∗ 2;
6 }
6.2 Arreglos 284
Del ejemplo anterior hay que notar que tanto i como j son variables locales al
for; para que esto suceda se requiere que la primera variable en una lista de este
estilo aparezca declarada con su tipo, y la segunda (tercera, etc.) variables deben
ser del mismo tipo; el formato debe ser de una única declaración que puede incluir
más de una variable del mismo tipo. Si alguna de las variables está declarada
antes y fuera del for aparecerá el mensaje de que se está repitiendo la declaración,
aunque esto no es exacto. Lo que sı́ es válido es tener varios enunciados for, cada
uno con una variable local i.
Por supuesto que el xenunciado simple o compuestoy puede contener o consistir
de, a su vez, algún otro enunciado for o while o lo que queramos. La ejecución va
a seguir el patrón dado arriba, terminando la ejecución de los ciclos de adentro
hacia afuera.
EJemplo 6.2.1
EJemplo 6.2.2
Supongamos ahora que queremos tener dos variables para controlar la itera-
ción, donde la primera nos va a decir en cuál iteración va (se incrementa de 1 en
1) y la segunda se calcula sumándose uno al doble de lo que lo que llevaba. El
encabezado del for serı́a:
285 Herencia
EJemplo 6.2.3
$ $ #
'
' '
'
'
'
'
' '
' Es la buscada
Registrar el lugar
'
' Buscar clave &
'
'
' (hasta encontrarla
Salir del ciclo
'
'
' o se acaben) ' ' `
'
' '
'
' !
'
& %Es la buscada Pasar al siguiente
Encontrar
clave ' ''
'
' !
'
'
' Encontrada Mostrar el nombre de la carrera
'
'
'
'
'
'
'
` !
'
% Encontrada Mostrar que el código es inválido
EJemplo 6.2.4
Podemos tener iteraciones anidadas, como lo dice la sintaxis del enunciado.
Por ejemplo, queremos producir un triángulo con la siguiente forma:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
En este caso debemos recorrer renglón por renglón, y en cada renglón reco-
rrer tantas columnas como renglones llevamos en ese momento. El código para
conseguir escribir esto se encuentra en el listado 6.6.
int [ ] [ ] matriz ; // 2 d i m e n s i o n e s : m a t r i z
EstudianteBasico [ ] [ ] [ ] f a c u l t a d ; // 3 d i m e n s i o n e s : cubo
Por la manera en que maneja Java los arreglos, si quiero inicializar un arreglo
en el momento de declararlo, digamos de dos dimensiones, tendré que darle entre
llaves las inicializaciones para cada uno de los renglones:
1 i n t [ ] [ ] [ ] cubos ;
2 c u b o s = new i n t [ 3 ] [ ] [ ] ;
3 c u b o s [ 0 ] = new i n t [ 6 ] [ ] ;
4 c u b o s [ 1 ] = new i n t [ 3 ] [ 3 ] ;
5 c u b o s [ 2 ] = new i n t [ 2 ] [ ] ;
6.2 Arreglos 290
(i.) Un arreglo es un objeto. Esto quiere decir que cuando declaramos un arreglo
simplemente estamos reservando espacio en memoria para la referencia, la
dirección en el heap donde se encontrarán los elementos del arreglo.
(ii.) Es una estructura de datos estática: una vez dado su tamaño, el arreglo no
puede achicarse o agrandarse.
(iii.) Podemos tener arreglos del número de dimensiones y del tipo que queramos.
(iv.) Los arreglos son estructuras de datos homogéneas, porque todos sus elemen-
tos son del mismo tipo.
(v.) Los arreglos de dos dimensiones no tienen porqué tener el mismo número
de elementos en cada renglón del arreglo. Esto se extiende en todas las
dimensiones que tenga el arreglo.
tamaño de una dimensión en un parámetro, pues el tipo del parámetro tiene que
ver nada más con el tipo de los elementos y el número de dimensiones; lo que
hay que especificar para cada parámetro de un método es, únicamente, el tipo
del parámetro. Lo mismo sucede con el valor que regresa un método: únicamente
hay que especificar el tipo del valor que regresa el método, que en el caso de los
arreglos, repetimos una vez más, consiste del tipo de los elementos y el número
de dimensiones.
Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (1/3)
1 package H e r e n c i a ;
2 import u t i l e s . Cadenas ; // Para métodos de c a d e n a s
3 /∗ ∗
4 ∗ C l a s e <code>A r r e g l o s </code >, i m p l e m e n t a v a r i o s métodos que t i e n e n
5 ∗ que v e r con a r r e g l o s .
6 ∗ En t o d o s e s t o s métodos , e l método no c o n o c e l o que l e e s t á n
7 ∗ pasando , p o r l o que t i e n e que t r a b a j a r ” i n d i r e c t a m e n t e ” .
8 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ v i s o ”> E l i s a V i s o G u r o v i c h </a>
9 ∗ @version 1.2
10 ∗/
11 public class Arreglos {
12 /∗ ∗
13 ∗ Suma d o s a r r e g l o s de d o s d i m e n s i o n e s . Los a r r e g l o s pueden
14 ∗ t e n e r d i s t i n t o número de e l e m e n t o s en c u a l q u i e r a de l a s
15 ∗ dimensiones .
16 ∗ @param A t i p o <code>i n t [ ] [ ] < / code >, p r i m e r a r r e g l o .
17 ∗ @param B t i p o <code>i n t [ ] [ ] < / code >, s e g u n d o a r r e g l o .
18 ∗ @ r e t u r n t i p o <code>i n t [ ] [ ] < / code >, un a r r e g l o con l a suma .
19 ∗/
20 p u b l i c i n t [ ] [ ] suma ( i n t [ ] [ ] A , i n t [ ] [ ] B) {
21 i n t min1 = Math . min (A . l e n g t h , B . l e n g t h ) ; // mı́nimo tamaño
22 i n t max1 = Math . max (A . l e n g t h , B . l e n g t h ) ; // máximo tamaño
23 i n t [ ] [ ] laSuma = new i n t [ max1 ] [ ] ;
24 /∗ I n v o c a m o s a q u i e n s a b e sumar a r r e g l o s de una d i m e n s i ó n ∗/
25 f o r ( i n t i =0; i < min1 ; i ++)
26 laSuma [ i ] = suma (A [ i ] , B [ i ] ) ;
27 // S i s e acabó a n t e s B
28 f o r ( i n t i = min1 ; i < max1 ; i ++) {
29 i f (A . l e n g t h > B . l e n g t h ) {
30 laSuma [ i ] = suma (A [ i ] , new i n t [ A [ i ] . l e n g t h ] ) ;
31 } else {
32 laSuma [ i ] = suma ( new i n t [ A [ i ] . l e n g t h ] , B [ i ] ) ;
33 }
34 }
35 r e t u r n laSuma ;
36 } // f i n suma ( i n t [ ] [ ] , i n t [ ] [ ] )
37
38 /∗ ∗
39 ∗ Suma d o s a r r e g l o s de una d i m e n s i ó n
293 Herencia
Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (2/3)
Código 6.7 Arreglos como parámetros y valor de regreso de una función Arreglos (3/3)
izquierdo de una asignación (lı́nea 26) y lo mismo sucede con cada uno de los
elementos en la lı́nea 48 para los elementos del arreglo result. Sin embargo, en la
lı́nea 23 no se dice cuántos elementos va a tener la segunda dimensión, porque
cuando suma regrese la referencia a un arreglo de una dimensión, esta referencia
se copiará en el lugar correspondiente (el arreglo de referencias de la primera
dimensión). Lo mismo sucede en la lı́nea 66, donde al regresar el método a un
arreglo de dos dimensiones, como lo que está regresando es una referencia, ésta
simplemente se copia.
Las m a t r i c e s a sumar s o n :
M a t r i z uno
3 4 5
2 8 7 5
3 4 7
Matriz dos
8 4 2 1
8 7 2 3
4 3 5 1
Suma de l a s d o s m a t r i c e s :
====================================
11 8 7 1
10 15 9 8
7 7 12 1
Por último, para redondear el uso de arreglos, presentamos una clase que tie-
ne algunos métodos estáticos relativos a arreglos. Esta clase se encuentra en el
listado 6.8.
6.2 Arreglos 296
1 package H e r e n c i a ;
2 import u t i l e s . Cadenas ;
3 /∗ ∗
4 ∗ C l a s e <code>A r r e g l o s 2 </code >, i m p l e m e n t a v a r i o s métodos que t i e n e n
5 ∗ que v e r con a r r e g l o s .
6 ∗ En t o d o s e s t o s métodos , e l método no c o n o c e l o que l e e s t á n
7 ∗ pasando , p o r l o que t i e n e que t r a b a j a r ” i n d i r e c t a m e n t e ” .
8 ∗/
9 public class Arreglos2 {
10 /∗ ∗
11 ∗ Método <code>suma</code >, suma l o s e l e m e n t o s de una m a t r i z p o r
12 ∗ columnas .
297 Herencia
63 return r e s u l t a d o ;
64 }
65
66 /∗ ∗
67 ∗ Método <code>sumaToda</code> suma a t o d o s l o s e l e m e n t o s de l a
68 ∗ m a t r i z . No s a b e c u a n t o s r e n g l o n e s hay n i e l numero de
69 ∗ e l e m e n t o s en cada columna .
70 ∗ @param m a t r i z v a l o r de t i p o <code>i n t </code> p a r a
71 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
72 ∗/
73 p u b l i c s t a t i c i n t sumaToda ( i n t [ ] [ ] m a t r i z ) {
74 i n t suma = 0 ;
75 f o r ( i n t i = 0 ; i < m a t r i z . l e n g t h ; i ++) {
76 f o r ( i n t j = 0 ; j < m a t r i z [ i ] . l e n g t h ; j ++) {
77 suma += m a t r i z [ i ] [ j ] ;
78 }
79 }
80 r e t u r n suma ;
81 }
82
83 /∗ ∗
84 ∗ Método <code>maximo</code> e n c u e n t r a e l v a l o r maximo en un
85 ∗ v e c t o r . No s a b e e l número de e l e m e n t o s en e l v e c t o r y como
86 ∗ pueden s e r t o d o s v a l o r e s n e g a t i v o s i n i c i a l i z a con e l p r i m e r
87 ∗ elemento del vector .
88 ∗ @param v e c t o r v a l o r de t i p o <code>i n t </code> p a r a
89 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
90 ∗/
91 p u b l i c s t a t i c i n t maximo ( i n t [ ] v e c t o r ) {
92 i n t maximo = v e c t o r [ 0 ] ;
93 f o r ( i n t i = 1 ; i < v e c t o r . l e n g t h ; i ++) {
94 i f ( maximo < v e c t o r [ i ] ) {
95 maximo = v e c t o r [ i ] ;
96 }
97 }
98 r e t u r n maximo ;
99 }
100 /∗ Prueba de l o s métodos ∗/
101 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
102 int [ ] [ ] laMatriz = {{2 ,3 ,1} ,{3} ,{5 ,2 ,8 ,4} ,{6 ,5 ,4 ,3 ,2 ,1} ,{3}};
103 i n t [ ] sumaCols = suma ( l a M a t r i z ) ;
104 System . o u t . p r i n t l n ( "Suma de columnas de matriz \n"
105 + "con renglones de distintos tama~ n os \n"
106 + "La matriz contiene :" ) ;
107 f o r ( i n t i = 0 ; i < l a M a t r i z . l e n g t h ; i ++) {
108 f o r ( i n t j = 0 ; j < l a M a t r i z [ i ] . l e n g t h ; j ++) {
109 System . o u t . p r i n t ( Cadenas . r e l l e n a
110 ( I n t e g e r . t o S t r i n g ( l a M a t r i z [ i ] [ j ] ) , ’i’ , ’ ’ , 4 ) +"\t" ) ;
111 }
112 System . o u t . p r i n t l n ( ) ;
299 Herencia
113 }
114 System . o u t . p r i n t l n ( " =========================================\ n"
115 + "La suma es:" ) ;
116 f o r ( i n t j = 0 ; j < sumaCols . l e n g t h ; j ++) {
117 System . o u t . p r i n t
118 ( Cadenas . r e l l e n a
119 ( I n t e g e r . t o S t r i n g ( sumaCols [ j ] ) , ’i’ , ’ ’ , 4 ) + "\t" ) ;
120 }
121 System . o u t . p r i n t l n ( ) ;
122 System . o u t . p r i n t l n ( " Ahora la suma por columnas " ) ;
123 sumaCols = sumaPorCols ( l a M a t r i z ) ;
124 f o r ( i n t j = 0 ; j < sumaCols . l e n g t h ; j ++) {
125 System . o u t . p r i n t ( Cadenas . r e l l e n a
126 ( I n t e g e r . t o S t r i n g ( sumaCols [ j ] ) , ’i’ , ’ ’ , 4 ) + "\t" ) ;
127 }
128 System . o u t . p r i n t l n ( ) ;
129 System . o u t . p r i n t l n ( " ================================== "
130 + "Una matriz generada aleatoriamente :" ) ;
131 i n t [ ] [ ] numeros = new i n t [ 5 ] [ 4 ] ;
132 f o r ( i n t i = 0 ; i < 5 ; i ++) {
133 f o r ( i n t j = 0 ; j < 4 ; j ++) {
134 // A s i g n a v a l o r e s a l e a t o r i o s a numeros [ i ] [ j ]
135 numeros [ i ] [ j ] = Math . r o u n d
136 ( ( f l o a t ) ( Math . random ( ) ∗ 1 0 0 0 0 ) ) % 1 0 1 7 ;
137
138 System . o u t . p r i n t ( Cadenas . r e l l e n a ( I n t e g e r
139 . t o S t r i n g ( numeros [ i ] [ j ] ) , ’i’ , ’ ’ , 6 ) ) ;
140 }
141 System . o u t . p r i n t l n ( ) ;
142 }
143 // P r i m e r o c a l c u l a un v e c t o r de máximos p o r r e n g l ó n
144 i n t [ ] deLosMax = {maximo ( numeros [ 0 ] ) , // I n i c i a l i z a
145 maximo ( numeros [ 1 ] ) ,
146 maximo ( numeros [ 2 ] ) ,
147 maximo ( numeros [ 3 ] ) ,
148 maximo ( numeros [ 4 ] ) } ;
149 // Ahora c a l c u l a e l máximo d e l v e c t o r de máximos
150 System . o u t . p r i n t l n ( "El maximisimo es :\t"
151 + maximo ( deLosMax ) ) ;
152 }
153 }
parámetros.
10 package H e r e n c i a ;
20 /∗ ∗
30 ∗ I n t e r f a c e <code>S e r v E s t B a s i c o </code> d e s c r i b e l o s s e r v i c i o s
40 ∗ que d a r á l a c l a s e <code>E s t u d i a n t e </code >.
50 ∗/
60 p u b l i c i n t e r f a c e S e r v E s t B a s i c o {
70 /∗ ∗
80 ∗ Método <code>getNombre </code> r e g r e s a e l v a l o r d e l a t r i b u t o
90 ∗ <code>nombre</code >.
100 ∗ @ r e t u r n v a l o r t i p o <code>S t r i n g </code >.
110 ∗/
120 p u b l i c S t r i n g getNombre ( ) ;
130
140 /∗ ∗
150 ∗ Método <code>g e t C u e n t a </code> r e g r e s a e l v a l o r d e l a t r i b u t o
160 ∗ <code>c u e n t a </code >.
170 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
180 ∗/
190 public S t r i n g getCuenta ( ) ;
200
210 /∗ ∗
220 ∗ Método <code>g e t C a r r e r a </code> r e g r e s a e l v a l o r d e l a t r i b u t o
230 ∗ <code>c a r r e r a </code >.
240 ∗ @ r e t u r n v a l o r de t i p o <code>i n t </code >.
250 ∗/
260 public int getCarrera ( ) ;
270
280 /∗ ∗
290 ∗ Método <code>setNombre </code> a c t u a l i z a e l v a l o r d e l a t r i b u t o
300 ∗ <code>nombre</code >.
310 ∗ @param n t i p o <code>S t r i n g </code >: nuevo v a l o r de nombre .
320 ∗/
330 p u b l i c v o i d setNombre ( S t r i n g n ) ;
340
350 /∗ ∗
360 ∗ Método <code>s e t C u e n t a </code> a c t u a l i z a e l v a l o r d e l a t r i b u t o
370 ∗ <code>c u e n t a </code >.
380 ∗ @param c t i p o <code>S t r i n g </code >: nuevo v a l o r de c u e n t a .
390 ∗/
400 public void setCuenta ( S t r i n g c ) ;
410
420 /∗ ∗
430 ∗ Método <code>s e t C a r r e r a </code> a c t u a l i z a e l v a l o r
303 Herencia
Entre los servicios ofrecidos por esta interfaz tenemos el método toString(),
que pretende armar el registro completo para impresión. Este método se hereda
de la clase Object que, por omisión, en caso de una variable primitiva la convierte
a cadena y en caso de una referencia, convierte a esta dirección también a cadena.
Como toda clase extiende a Object, y lo que pretende este método es convertir al
objeto en una cadena observable, lo que hacemos es “redefinir” el método para
que en todos lados donde se invoque –por ejemplo, dentro del método println– se
use esta nueva manera de mostrar el estado del objeto en cuestión. Notarán la
ausencia del método armaRegistro() que es al que sustituye toString(). Como el
objetivo de la herencia es, precisamente, reutilizar, elegimos redefinir el método
toString() de la superclase Object para producir una cadena que refleje el contenido
de objetos de la clase EstudianteBasico –lı́neas 460–. La clase Object es la raı́z del
árbol de herencia: toda clase extiende a la clase Object.
Dados estos servicios, tenemos una clase que los ofrece, EstudianteBasico, que
quedarı́a definida como se muestra en el listado 6.10 en la página opuesta. Omi-
timos los comentarios para tener un código más fluido y porque son métodos
que responden a lo especificado en la interfaz. únicamente comentaremos aquellos
métodos adicionales a los presentados en la interfaz.
305 Herencia
460 p u b l i c E s t u d i a n t e B a s i c o ( S t r i n g nmbre ,
470 S t r i n g cuenta , i n t c a r r e r a ) {
480 nombre = nmbre == n u l l ? "" : nmbre ;
490 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a ;
500 i f ( CatalogoCarreras . esCarrera ( carrera )) {
510 this . carrera = carrera ;
520 } // S i no l e ponemos nada queda en 0
530 }
810 p u b l i c v o i d s e t C u e n t a ( S t r i n g newCuenta ) {
820 t h i s . c u e n t a = newCuenta == n u l l ? "" : newCuenta ;
830 }
1130 p u b l i c S t r i n g getNombre ( ) {
1140 r e t u r n nombre ;
1150 }
1270 /∗ ∗
1280 ∗ Método <code>t o S t r i n g </code >: p a r a no p e r d e r a c c e s o a l
1290 ∗ método de l a s u p e r c l a s e .
1300 ∗ @param c t i p o <code>i n t </code >: p a r a d i s t i n g u i r métodos .
1310 ∗ @ r e t u r n t i p o <code>S t r i n g </code >: c a d e n a de l a s u p e r c l a s e .
1320 ∗/
1330 public String toString ( int c ) {
1340 r e t u r n super . t o S t r i n g ( ) ;
1350 }
1270 /∗ ∗
1280 ∗ Método <code>t o S t r i n g </code >, r e d e f i n e e l mismo método en l a
1290 ∗ c l a s e O b j e c t y s u s t i t u y e a l método a r m a R e g i s t r o d e l c a p i t u l o
1300 ∗ anterior .
1310 ∗ @ r e t u r n t i p o <code>S t r i n g </code >.
1320 ∗/
1330 public String toString () {
1340 S t r i n g l i n e a = daNombre ( )
1350 + " " + daCuenta ( )
1360 + " " + daCarrera ( ) ;
1370 return l i n e a ;
1380 }
1560 p u b l i c S t r i n g daCampo ( i n t c u a l ) {
1570 switch ( c u a l ) {
1580 case NOMBRE: r e t u r n daNombre ( ) . t r i m ( ) ;
1590 case CUENTA : r e t u r n daCuenta ( ) . t r i m ( ) ;
1600 case CARRERA : return daCarrera ( ) . trim ( ) ;
1610 default : r e t u r n " Numero de campo invalido " ;
1620 }
1630 }
1710 p u b l i c v o i d ponCampo ( i n t c u a l , S t r i n g v a l o r ) {
1720 i f ( v a l o r == n u l l ) { // Para no m a n e j a r r e f e r e n c i a s n u l a s .
1730 v a l o r = "" ;
1740 }
1750 switch ( c u a l ) {
1760 case NOMBRE: setNombre ( v a l o r ) ;
1770 break ;
1780 case CUENTA : setCuenta ( v a l o r ) ;
1790 break ;
1800 case CARRERA :
1810 int intValor = Integer . parseInt ( valor );
1820 i f ( CatalogoCarreras . esCarrera ( intValor )) {
1830 setCarrera ( intValor );
1840 } else {
1850 setCarrera (0);
1860 }
6.3 Aspectos principales de la herencia 308
1990 p u b l i c S t r i n g daNombre ( ) {
2000 r e t u r n Cadenas . r e l l e n a ( nombre , ’i’ , ’ ’ , TAM NMBRE ) ;
2010 }
2190 p u b l i c S t r i n g daCuenta ( ) {
2200 S t r i n g sCuenta = S t r i n g . valueOf ( cuenta ) ;
2210 i f ( s C u e n t a . l e n g t h ( ) < TAM CTA) {
2220 r e t u r n Cadenas . r e l l e n a ( sCuenta , ’d’ , ’0’ , TAM CTA ) ;
2230 }
2240 else {
2250 r e t u r n s C u e n t a . s u b s t r i n g ( 0 ,TAM CTA ) ;
2260 }
2270 }
2390 p u b l i c v o i d p o n R e g i s t r o ( S t r i n g nmbre , i n t c a r r e r a , S t r i n g c u e n t a ) {
2400 nombre = nmbre == n u l l ? "" : nmbre . t r i m ( ) ;
2410 t h i s . c u e n t a = c u e n t a == n u l l ? "" : c u e n t a . t r i m ( ) ;
2420 this . setCarrera ( carrera );
2430 }\ v s { .75 ex }
Lo único que debemos notar es el uso del acceso protected en los atributos de
esta clase. Cuando tenemos extensión de clases, como pretendemos hacer ahora,
tiene sentido hablar de este modo de acceso que hemos mencionado poco hasta
ahora. Este tipo de acceso se usa para cuando queremos que las clases que extien-
den a la actual puedan tener acceso a ciertas variables o métodos, ya que funciona
como si fuera acceso público para las clases que extienden, pero como si fuera
privado para las clases que no extienden a la clase actual. Los datos con acceso
privado no son accesibles más que para la clase actual. El acceso protegido fun-
ciona como el privado para clases que no heredan y como público para las clases
que heredan.
Se nos ocurren distintos tipos de listas que tomen como base a EstudianteBasico.
Por ejemplo, un listado que le pudiera servir a una biblioteca tendrı́a, además de la
información de EstudianteBasico, los libros en préstamo. Un listado para la División
309 Herencia
En todos los casos que listamos, la información original debe seguir estando
presente, ası́ como los métodos que tiene la superclase. Debemos indicar, entonces,
que la clase que se está definiendo extiende a la superclase, heredando por lo tanto
todo aquello declarado como protegido, público o de paquete. La sintaxis en Java
se muestra a continuación.
Sintaxis:
xsubclase que hereday ::= class xsubclasey extends xsuperclasey {
xdeclaraciones de campos y métodosy
}
Semántica:
La xsubclasey es una nueva declaración, que incluye (hereda) a todos los
campos de la superclase (no–privados), junto con todos sus métodos. Las
xdeclaraciones de campos y métodosy se refiere a lo que se desea agregar a la
definición de la superclase en esta subclase. También colocamos ahı́ firmas
de métodos que existen en la superclase pero queremos funcionen un poco
distinto en la subclase; atributos cuyo nombre aparece en la superclase y
que pueden o no cambiar de tipo; y, en general, todas aquellas declaraciones
cuyo nombre o firma ya aparezca en la superclase pero queremos que tenga
otro significado en la subclase.
Pongamos por ejemplo la programación de una subclase para las listas con cali-
ficaciones. El registro deberá contener lo que contiene EstudianteBasico, agregándo-
le nada más lugar para las calificaciones de los exámenes. El encabezado de la clase
quedarı́a como se ve en el listado 6.11 en la siguiente página.
6.3 Aspectos principales de la herencia 310
80 p u b l i c c l a s s E s t u d i a n t e C a l i f s extends E s t u d i a n t e B a s i c o {
/∗ D e c l a r a c i o n e s a d i c i o n a l e s ∗/
...
/∗ A g r e g a rı́ a m o s l o s métodos que t i e n e n que v e r con l a s ∗/
/∗ c a l i f i c a c i o n e s y a q u e l l o s a t r i b u t o s que hayamos ∗/
/∗ a g r e g a d o . ∗/
...
99990 }
6.4 Polimorfismo
Una de las grandes ventajas que nos ofrece la herencia es poder manipular
subclases a través de las superclases, sin que sepamos concretamente de cuál sub-
clase se trata. Supongamos, para poner un ejemplo, que declaramos otra subclase
de EstudianteBasico que se llama EstudianteBiblio y que de manera similar a como
lo hicimos con EstudianteCalifs extendemos adecuadamente y redefinimos nueva-
mente el método toString(). Podrı́amos tener el código que sigue:
10 EstudianteBasico [ ] estudiantes = {
20 new E s t u d i a n t e C a l i f s ( " Pedro ’’,. . . ),
30 new EstudianteBiblio (. . . )
40 };
6.4 Polimorfismo 316
En estas lı́neas declaramos un arreglo con dos elementos del tipo EstudianteBasico,
donde el primero contiene a un EstudianteCalifs mientras que el segundo contiene
a un EstudianteBiblio. Como las subclases contienen todo lo que contiene la super-
clase, y como lo que guardamos son referencias, podemos guardar en un arreglo de
la superclase elementos de las subclases. Es más; si hacemos la siguiente solicitud
S t r i n g cadena = e s t u d i a n t e s [ 0 ] . t o S t r i n g ( ) ;
la máquina virtual de Java, en ejecución, se da cuenta que lo que tiene ahı́ es una
referencia a algo del tipo EstudianteCalifs, por lo que utilizará la implementación
dada en esa subclase para dar respuesta a esta solicitud. Decimos que gobierna
el tipo del objeto construido, no el tipo de la declaración. La decisión de cuál
de las implementaciones de toString() debe ser invocada se toma en ejecución, ya
que el tipo de la referencia guardada en la localidad del arreglo depende de la
secuencia de ejecución . A esta capacidad de los lenguajes orientados a objetos
de resolver dinámicamente el significado de un nombre de método es a lo que se
llama polimorfismo, ya que el mismo nombre (de hecho, la misma firma) puede
tomar significados distintos dependiendo del estado del programa y de la clase a
la que vaya dirigido el mensaje (la llamada o solicitud).
El operador instanceof
Supongamos que estamos en la sección escolar de la Facultad de Ciencias,
donde tienen registros de alumnos de distintos tipos; supongamos asimismo que
le piden al coordinador que por favor entregue una lista de todos los registros que
tiene para la biblioteca. El coordinador deberá tener un mecanismo que identifique
de qué clase es cada uno de los objetos que se encuentran en la lista (arreglo,
lista ligada, etc.). El operador instanceof hace exactamente esto. Es un operador
binario, donde las expresiones que lo usan toman la siguiente forma:
que regresa el valor booleano verdadero si, en efecto, el objeto dado en la expresión
de la izquierda es un ejemplar de la clase dada a la derecha; y falso si no. Para un
proceso como el que acabamos de describir tendrı́amos un código como el que se
puede observar en el listado 6.14 en la página opuesta –suponiendo que tenemos
los registros en un arreglo de registros–.
317 Herencia
1470 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
1480 /∗ Prueba de l a c l a s e ∗∗∗∗∗ ∗/
1490 /∗ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗/
1500 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
1510 E s t u d i a n t e B a s i c o [ ] miCurso = new E s t u d i a n t e B a s i c o [ 4 ] ;
1520 Object [ ] misObjetos = new O b j e c t [ 4 ] ;
1530 miCurso [ 0 ] = new E s t u d i a n t e C a l i f s ( ) ;
1540 misObjetos [ 0 ] = new O b j e c t ( ) ; ;
1550 miCurso [ 1 ] = new E s t u d i a n t e B a s i c o ( ) ;
1560 misObjetos [ 1 ] = new E s t u d i a n t e B a s i c o ( ) ;
1570 miCurso [ 2 ] = new E s t u d i a n t e C a l i f s ( " Elisa " , " 56432 " , 1 2 7 , 5 ) ;
1580 m i s O b j e t o s [ 2 ] = new E s t u d i a n t e C a l i f s ( " Elisa " , " 56432 " , 1 2 7 , 5 ) ;
1590 miCurso [ 3 ] = new E s t u d i a n t e C a l i f s ( " Jorge " , "34" , 1 0 6 , 4 ) ;
1600 m i s O b j e t o s [ 3 ] = new E s t u d i a n t e C a l i f s ( " Jorge " , "34" , 1 0 6 , 4 ) ;
1610 // ( ( E s t u d i a n t e C a l i f s ) m i s O b j e t o s [ 0 ] ) . c o p i a C a l i f s ( c a l i f s ) ;
1620 float [ ] c a l i f s = {8.7 f , 6 . 8 f , 9 . 6 f ,8 f ,7 f };
1630 ( ( E s t u d i a n t e C a l i f s ) miCurso [ 2 ] ) . s e t C a l i f s ( c a l i f s ) ;
1640 miCurso [ 0 ] . setNombre ( "Juan" ) ;
1650 miCurso [ 0 ] . s e t C u e n t a ( "987" ) ;
1660 ( ( E s t u d i a n t e C a l i f s ) miCurso [ 0 ] ) . c o p i a C a l i f s
1670 ( ( ( E s t u d i a n t e C a l i f s ) miCurso [ 2 ] ) . g e t C a l i f s ( ) ) ;
1680 miCurso [ 1 ] . setNombre ( "Pepe" ) ;
1690 miCurso [ 1 ] . s e t C a r r e r a ( 2 0 1 ) ;
319 Herencia
10 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a H e r e n c i a / E s t u d i a n t e
20 C a l i f s
30 E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . C l a s s C a s t E x c e p t i o n : j a v a . l a n g . O b j e c t
c a n n o t be c a s t t o H e r e n c i a . E s t u d i a n t e C a l i f s
40 a t H e r e n c i a . E s t u d i a n t e C a l i f s . main ( E s t u d i a n t e C a l i f s . j a v a : 1 5 6 )
50
60 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $ j a v a H e r e n c i a / E s t u d i a n t e C a l i f s
70
80 // E j e c u c i ó n con l a l i n e a 156 comentada
90
100 miCurso [ 0 ] , c l a s e : E s t u d i a n t e C a l i f s
110 Juan 987000000 C o d i g o i n v a l i d o
8.70 6.80 9.60 8.00 7.00 8.02
6.5 Clases abstractas 320
250 J o r g e 340000000 F i s i c a
0.00 0.00 0.00 0.00 0.00
Supongamos que tenemos una jerarquı́a de clases para las figuras geométricas,
que se podrı́a ver como en la figura 6.21 en la siguiente página. La superclase es
FiguraGeometrica y reconocemos que toda figura geométrica debe tener los siguien-
tes servicios:
dibujarse,
moverse,
borrarse,
crearse.
321 Herencia
Figura geométrica
Sintaxis:
xencabezado de clase abstractay ::= abstract class xidentificadory
Semántica:
Le estamos indicando al compilador dos caracterı́sticas:
No pueden crearse objetos de esta clase.
Contiene al menos un método cuya implementación no está definida.
/∗ S i g u i e n t e n i v e l de l a j e r a r q uı́ a . También a b s t r a c t a ∗/
a b s t r a c t c l a s s L i n e a extends F i g u r a G e o m e t r i c a {
abstract public void l o n g i t u d ( ) ;
......
}
......
/∗ T e r c e r n i v e l de l a j e r a r q uı́ a . C l a s e c o n c r e t a ∗/
c l a s s R e c t a extends L i n e a {
public void p i n t a ( ) {
...
}
...
}
...
323 Herencia
No hay la obligación de que todos los métodos en una clase abstracta sean
abstractos. Dependerá del diseño y de las posibilidades que tenga la superclase
para definir algunos métodos para aquellas clases que hereden. Aun cuando la
clase no tenga métodos abstractos, si queremos que no se creen objetos de esa
clase la declaramos como abstracta.
6.6 Interfaces
La herencia en Java es simple, esto es, cada clase puede heredar de a lo más
una superclase. Pero muchas veces necesitamos que hereden de más de una clase.
Las interfaces corresponden a un tipo, como las clases, que definen exclusivamente
comportamiento. Lo único que pueden tener las interfaces son constantes estáticas
y métodos abstractos, por lo que definen el contrato que se establece con aquellas
clases que implementen esa interfaz. Se dice que una clase implementa una interfaz,
porque cuando una clase hereda de una interfaz debe dar la implementación de los
métodos declarados en la interfaz. La sintaxis para la declaración de una interfaz,
como ya hemos visto, es:
Sintaxis:
xencabezado de interfazy ::= interface xidentificadory
...
Semántica:
Se declara un tipo que corresponde a constantes y encabezados de métodos.
Como todo lo que se declare dentro de una interfaz es público, pues corresponde
siempre a lo que puede hacer un objeto, no se usa el calificativo public en los
enunciados dentro de la declaración de la interfaz. Como forzosamente todos los
métodos son abstractos, ya que no tienen implementación, tampoco se pone este
calificativo frente a cada método. Y como sólo se permiten constantes estáticas, los
calificativos de static y final se omiten en las declaraciones de constantes dentro
de una interfaz. Por lo tanto, las constantes y métodos en una interfaz serán
declarados nada más con el tipo de la constante o el tipo de valor que regrese el
método, los identificadores y, en el caso de las constantes el valor; en el caso de
los métodos, los parámetros.
Pensemos en el comportamiento de una lista, como las que hemos estado vien-
do. Sabemos que no importa de qué sea la lista, tiene que tener un método que
6.6 Interfaces 324
agregue, uno que busque, etc. Dependiendo de los objetos en la lista y de la imple-
mentación particular, la implementación de cada uno de los métodos puede variar.
Tenemos acá un caso perfecto para una interfaz. En el listado 6.18 podemos ver
la declaración de una interfaz de este tipo.
Dado que las interfaces corresponden a tipos, igual que las clases, podemos
declarar variables de estos tipos. Por ejemplo,
Lista miLista ;
y usarse en cualquier lugar en que se pueda usar un objeto de una clase que imple-
menta a la interfaz Lista, de la misma manera que se puede usar una variable de la
clase Object en lugar de cualquier objeto. Sin embargo, si deseamos que el objeto
sea visto como la subclase tendremos que aplicar lo que se conoce como “casting”’,
325 Herencia
El método busca nos regresa un objeto de tipo Object (la referencia), pero
sabemos, por la implementación de busca, que en realidad nos va a regresar una
referencia a un objeto de tipo EstudianteCalifs, por lo que podemos aplicar el
casting.
Una clase dada puede extender a una sola superclase, pero puede implementar
a tantas interfaces como queramos. Como no tenemos la implementación de los
métodos en las interfaces, aun cuando una misma firma aparezca en más de una
interfaz (o inclusive en la superclase) la implementación que se va a elegir es la
que aparezca en la clase, por lo que no se presentan conflictos.
Las interfaces pueden extender a una o más interfaces de la misma manera
que subclases implementan a una o más interfaces (como lo que se da en una
interfaz es únicamente firmas y constantes, no habrá conflicto). En caso de que
haya declaradas constantes con el mismo nombre, pero tipos distintos o valores
distintos en más de una interfaz, el compilador exigirá que se discrimine de cuál
de las interfaces se está hablando. Como las interfaces no ejecutan código, más
que en posibles asignaciones a constantes, se debe hacer explı́cito a cuál de los
varios valores nos estamos refiriendo, precediendo el valor con el nombre de la
interfaz –en realidad, siempre que queramos usar alguna constante de una interfaz
tendremos que dar el nombre completo de la constante, que incluye el nombre de
la interfaz–. La sintaxis es la misma que con la extensión de clases, excepto que
permite extender a más de una interfaz.
Sintaxis:
interface xidentif1 y extends xidentif2 y, . . . , xidentifn y
Semántica:
De la misma manera que con las clases, la sub-interfaz hereda todas las
definiciones de los métodos de las súper-interfaces.
Ejercicios
6.2.- Escribe un método en Java que, usando el enunciado for, construya una
matriz triangular izquierda donde en cada entrada de la matriz coloque el
número de renglón. El método debe imprimir esta matriz en la consola. El
método recibe como parámetro el número de renglones. El primer renglón
tiene una columna con un 1, el segundo dos con 2, y ası́ sucesivamente. El
algoritmo se muestra en el diagrama de Warnier que sigue.
$ # #
'
'
'
'
'
'
Inicio Construir renglón Construir columna
'
'
'
(k) (i+1)
'
'
' # #
&
Matriz triangular Llenar renglón Llenar columna triangulo[i,j] Ð i+1
con k renglones '' (i=0,. . . ,k) (j=0,. . . ,i)
'
'
' $ $
'
'
' & &Imprimir columna
'
'
' Imprimir renglón (j=0,. . . ,i)
'
%Final % (i =0,. . . ,k-1) %Cambiar de renglón
327 Herencia
6.3.- Construye una matriz triangular de caracteres que recibe como parámetro el
número de columnas en el último renglón (debe ser impar), y al imprimirla
se muestre de la siguiente forma:
a
b b b
c c c c c
d d d d d d d
e e e e e e e e e
6.4.- Escribe un método en Java que recibe como parámetros dos arreglos de en-
teros de una dimensión y regresa un arreglo de enteros de una dimensión que
consiste de la concatenación del segundo arreglo a continuación del primero.
El último elemento de cada uno de los arreglos es el valor que corresponde
a un Integer.MIN VALUE, que el el menor valor que puede almacenar una
variable de tipo int. El arreglo nuevo no debe contener ninguna celda vacı́a
ni debe aparecer este número que se usa como centinela.
0 0 0 0 0 1 1 2 2 2 4
La tabla me dice que deben aparecer cinco ceros, seguidos de dos unos,
seguidos de tres doses, cero treses y por último un cuatro.
6. Ejercicios 328
Escribe una subclase PesoCaja de Caja que defina el peso de las cajas, agre-
gando un atributo peso que defina el peso por centı́metro cuadrado de la
caja.
329 Herencia
6.9.- Define una subclase de PesoCaja que calcule el costo de enviar una caja como
el mı́nimo entre un costo base y el costo calculado como el peso de la caja
por un costo por kilogramo que pese la caja.
Figura
Circulo Rectangulo
Cuadrado
a) Proporciona los constructores para cada una de las clases que extienden
a Figura.
c) Escribe un método main en la clase Circulo que pruebe que todas tus
clases que no son abstractas funcionan bien y calculan bien el área.
Recorremos los elementos del arreglo original para ocupar el lugar del
elemento copiado.
Al terminar (cuando no queden elementos válidos en el arreglo original)
regresamos el arreglo nuevo.
Escribe el método en Java que hace este ordenamiento (puede ser estática).
El diagrama de Warnier para este método se encuentra a continuación.
$ $
'
'
' '
' posMenor Ð 0
'
' '
'
'
'
'
' '
'
' menor Ð primera figura
'
'
' &
'
'
' Inicio Construye un arreglo nuevo
'
'
' '
' del mismo tamaño
' '
'
'
'
' '
'
' lugarEnNuevo Ð 0
'
'
' '
%
'
' hayEnViejo Ð tamaño del arreglo
'
'
' $
'
' $ #
'
'
' '
'
'
'
'
' posMenor Ð j
'
' '
' '
' actual <menor
Ordena & '
' & menor Ð figs[j]
Figuras '
'
' Elige menor
por área '
'
'
'
'
'
' (j=0,.., '
'
' ` !
'
'
' '
& '
'
%
'
'
'
Procesa menor hayEnViejo) actual <menor H
'
' (i=1...núm. de
'
'
' elementos) ' '
' Copia menor a arreglo nuevo
'
' '
'
'
'
'
' '
' Recorre elementos a ocupar lugar
'
' '
'
'
'
'
' '
'
'
' '
'
%
lugarEnNuevo ++
'
'
' hayEnViejo –
'
'
'
'
' "
'
'
%Final Entrega arreglo de
figuras ordenado
6.12.- Agrega la figura Triangulo a la jerarquı́a de clases dada y escribe el código
de Java necesario para que sea tomada en cuenta en UsoDeFiguras.
6.13.- Agrega la figura Rombo a la jerarquı́a de clases dada y escribe el código de
Java necesario para que sea tomada en cuenta en UsoDeFiguras. Se puede
agregar como subclase de rectángulo, pero calculando el área de manera
distinta.
6.14.- Agrega la figura Trapecio a la jerarquı́a de clases dada y escribe el código
de Java necesario para que sea tomada en cuenta en UsoDeFiguras.
Administración de
la memoria durante 7
ejecución
igual que los parámetros, se tiene acceso –de ahı́ viene el nombre de locales–. Adi-
cionalmente a esto, cuando se abren bloques de enunciados en las condicionales o
en las iteraciones, se pueden declarar variables que únicamente son locales dentro
de ese bloque de enunciados. Sólo son conocidas dentro del bloque1 .
método
método
bloque
bloque
método
bloque
método
bloque
bloque
Esto nos define dos niveles lexicográficos distintos: el global, que se refiere a
lo que se puede utilizar desde cualquier punto de cualquiera de nuestras clases,
dados los permisos adecuados, y el local, que es aquello que se encuentra dentro
de un método. Adicionalmente, dentro de los métodos podemos tener bloques
de enunciados que incluyan declaraciones. Estas declaraciones son únicamente
visibles dentro del bloque de instrucciones.
Para los métodos estáticos, como es el caso del método main de las clases,
esto funciona un poco distinto, ya que este tipo de métodos no tiene acceso más
1
Sin embargo, en el caso de una variable declarada en el encabezado de una iteración for, si
existe alguna declaración de una variable con el mismo nombre fuera del bloque y que la precede,
el compilador dará error de sintaxis por identificador ya declarado.
337 Administración de la memoria durante ejecución
El rango de una variable está definido estáticamente, pues está dado por la
estructura del programa: de ver el listado podemos decir cuál es el rango de una
variable dada y definirlo como público (global, pero atado a un objeto); local
(declarado dentro de un método) o como privado para una clase. Es el compilador
el que se encarga de resolver todo lo relacionado con el rango de las variables,
siguiendo para esto la estructura de bloques.
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
...
30 p u b l i c v o i d A( i n t i ) {
...
40 B( i , a ) ;
...
50 }
main [1]
Constructor de objeto [2]
A(10) [2]
B(10,3) [3]
C( ) [4]
B(3,2) [2]
C( ) [3]
C( ) [2]
Por ejemplo, dentro del método B en el que se le asigna valor a una variable
entera a –lı́nea 70– y que es variable local a B, el valor del atributo a no se va a
ver modificado. Por ello, en la llamada de la lı́nea 170 al método B, los valores
con los que es llamado son los originales de a y b, o sea 3 y 2.
Sigue un diagrama de Warnier en la figura 7.3 que nos muestra la relación
entre las llamadas.
Lo que me indican los dos diagramas anteriores, es que la ejecución del pro-
grama debe proseguir de la siguiente manera:
1. Entrar a ejecutar main.
2. Entrar a ejecutar el constructor de Cualquiera.
2. Salir de ejecutar el constructor de Cualquiera.
2. Entrar a ejecutar el método A(10).
3. Entrar a ejecutar el método B(10,3).
4. Entrar a ejecutar el método C().
4. Salir de ejecutar el método C().
3. Salir ejecutar el método B(10,3).
2. Salir de ejecutar el método A(10).
2. Entrar a ejecutar el método B(3,2).
3. Entrar a ejecutar el método C().
3. Salir de ejecutar el método C().
2. Salir ejecutar el método B(3,2).
2. Entrar a ejecutar el método C().
2. Salir de ejecutar el método C().
1. Salir de ejecutar main.
Tanto en el esquema como en la secuencia de ejecución (donde omitimos para
cada función la ejecución de lo que no fuera llamada a método), asociamos un ente-
ro a cada llamada. Esto es con el objeto de identificar los anidamientos dinámicos
–los que están definidos por la secuencia de ejecución del programa–. Este esque-
ma muestra varios aspectos importantes que tienen que ver con la ejecución de un
programa. Revisemos algunos de ellos:
1. El anidamiento dinámico (en ejecución) no forzosamente coincide con el
estático (sintáctico). Mientras que lexicográficamente hablando únicamen-
te tenemos el nivel global y el local, dinámicamente podemos tener tantos
niveles como queramos, uno por cada vez que desde dentro de una función
llamamos a otra.
2. La última rutina a la que entramos es la primera de la que salimos.
3. Cuando aparece una función f como argumento de una función g, la llamada
a f se inicia y termina antes que la llamada a g. Para poder llamar a g
debemos tener el valor de sus argumentos, por lo que es necesario que antes
de entrar a g obtengamos el valor de f.
4. El nivel dinámico que le corresponde a una función f que aparece como
argumento de una función g es el mismo que el de la función g.
Para poder hacer esto, la ejecución del programa se lleva a cabo en la memo-
ria de la máquina, organizada ésta como una pila –stack en inglés–, que es una
341 Administración de la memoria durante ejecución
a) Respecto a su estructura:
La estructura es lineal, esto es, podemos pensarla con sus elementos “for-
mados” uno detrás del otro.
Es una estructura homogénea, donde todos sus elementos son del mismo
tipo.
Es una estructura dinámica, esto es, crece y se achica durante ejecución.
Tiene asociado un tope, que corresponde al último elemento que se co-
locó en la pila.
b) Respecto a su uso:
tope
... ...
crece ... ...
hacia
... ...
“arriba”
... ...
... ...
stack o pila
clase que se va a ejecutar, que incluye los nombres de las clases accesibles y las
variables y métodos de la clase que se va a ejecutar. A esto le llamamos el paso 0
en la ejecución de un programa.
Cuando se está ejecutando un programa se puede invocar un método desde
distintos puntos del programa. En el punto de llamada de un método la ejecución
debe transferirse a ejecutar ese método y una vez terminada le ejecución del mismo
regresar al punto desde donde se hizo la invocación, para continuar con la ejecución
del programa. A la posición en la que se encuentra la llamada se le conoce como
punto de llamada e indica el punto al que debe regresar la ejecución del programa
una vez que termine la función. Esta última caracterı́stica hace que se le utilice
como dirección de regreso. La ejecución del programa, como ya mencionamos, se
lleva a cabo en la pila. Cada vez que se invoca a un método –el programa principal
main es una función invocada por el sistema operativo– se tiene que “montar” al
método en la pila, anotando muy claramente a dónde debe regresar la ejecución
al terminar la rutina. Al terminar la ejecución del método, se “desmonta” de la
pila al método. Para “montar” un método a la pila hay que construir lo que se
conoce como su registro de activación, que es una tabla en la que hay lugar para
los parámetros y las variables locales del método, de tal manera que durante la
ejecución se encuentren siempre en la parte superior de la pila.
Al invocar un método (para transferirse a ejecutarlo), el sistema debe realizar
los siguientes pasos:
1. Dejar un lugar en la pila para que el método coloque ahı́ el valor que va a
regresar, si es que regresa valor.
2. Hacer la marca en la pila, copiando ahı́ el contenido del contador del pro-
grama, que corresponde al punto de regreso.
3. Buscar en la pila, en el registro de activación global, la dirección de código
donde se encuentra definido ese método. Copiar esa dirección al contador del
programa (es donde va a iniciar la ejecución del método invocado cuando se
termine de montarlo en la pila).
4. Construir el registro de activación del método, dejando un lugar para cada
parámetro, en el orden en que están declarados, y un lugar para cada variable
local (o estructura de datos pública o privada, si se trata de una clase).
5. Evaluar los argumentos, para entregarle al método una lista de valores e
irlos colocando en el registro de activación.
6. Copiar a la pila, en el orden en que aparece, el registro de activación (el
último es el que queda en el tope de la pila).
7. Copiar los valores de los argumentos a los lugares para los parámetros en el
registro de activación.
7.1 La pila y el heap 344
10 c l a s s C u a l q u i e r a {
xCualquieray objeto @heap (a=3,b=2) 20 private int a = 3 , b = 2;
xinty n *5 30 p u b l i c v o i d A( i n t i ) {
xinty m 40 B( i , a ) ;
*10
50 }
xString r sy args @heap (?) 60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
d.r. Sistema 80 C();
Operativo 90 }
main 100 public void C( ) {
xvoidy main #130 110 int k = 2 ∗ a ;
xvoidy C #100 120 }
xvoidy B #60 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 i n t m = 10 , n = 5 ;
xvoidy A #30 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xinty b 2 160 o b j e t o . A(m) ;
xinty a 3 170 o b j e t o . B(m, n ) ;
180 objeto .C( ) ;
d.r. Sistema 190 }
Operativo 200 }
clase Cualquiera
xinty a 13 10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty j 3 30 p u b l i c v o i d A( i n t i ) {
xinty i 10 40 B( i , a ) ;
dirección de 50 }
regreso: #40 60 p u b l i c v o i d B( i n t i , i n t j ) {
B(10,3) 70 int a = i + j ;
xinty i 10 80 C();
dirección de 90 }
regreso: #160
A(10) 100 public void C( ) {
xCualquieray objeto @heap (a=3,b=2) 110 int k = 2 ∗ a ;
120 }
xinty n 5 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xinty m 10 ...
200 }
Al llegar a la lı́nea de código #80 hay una llamada desde B al método C, por
lo que nuevamente se marca la pila, se actualiza el contador del programa
y se monta en la pila el registro de activación de C(). El resultado de estas
acciones se pueden ver en la figura 7.12.
10 c l a s s C u a l q u i e r a {
xintya 13 20 private int a = 3 , b = 2;
xinty j 3 ...
xinty i 10 60 p u b l i c v o i d B( i n t i , i n t j ) {
dirección de 70 int a = i + j ;
regreso: #40 80 C();
B(10,3) 90 }
xinty i 10
100 public void C( ) {
dirección de 110 int k = 2 ∗ a ;
regreso: #160
A(10) 120 }
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xCualquieray objeto @heap (a=3,b=2) ...
200 }
Se llega al final del método A(10), por lo que se desmonta el registro de ac-
tivación de A(10), se copia la dirección de regreso de la marca al contador del
programa y quita la marca de la pila. Podemos observar el estado de la pila en
este momento en la figura 7.15.
7.1 La pila y el heap 350
Al llegar la ejecución del programa a la lı́nea 170 se encuentra con otra invo-
cación a B(3,2), que son los campos de la clase. Se coloca la marca en la pila con
dirección de regreso 180, se actualiza el PC para que marque el inicio del método
B y se monta a la pila el registro de activación de B(3,2). Los resultados de estas
acciones se muestran en la figura 7.16.
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xintya 15 30 p u b l i c v o i d A( i n t i ) {
xinty j 5 40 B( i , a ) ;
xinty i 10 50 }
dirección de 60 p u b l i c v o i d B( i n t i , i n t j ) {
regreso: #170 70 int a = i + j ;
B(10,5) 80 C();
xCualquieray objeto @heap (a=3,b=2) 90 }
xinty n 5 100 public void C( ) {
110 int k = 2 ∗ a ;
xinty m 10 120 }
xString r sy args @heap (?) 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }
351 Administración de la memoria durante ejecución
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
xinty k 6 30 p u b l i c v o i d A( i n t i ) {
dirección de 40 B( i , a ) ;
regreso: #80 50 }
C() 60 p u b l i c v o i d B( i n t i , i n t j ) {
xintya 15 70 int a = i + j ;
xinty j 5 80 C();
xinty i 10 90 }
100 public void C( ) {
dirección de
regreso: #170 110 int k = 2 ∗ a ;
B(10,5) 120 }
xCualquieray objeto @heap (a=3,b=2) 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
200 }
60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
80 C();
90 }
100 public void C( ) {
110 int k = 2 ∗ a ;
xCualquieray objeto @heap (a=3,b=2) 120 }
xintyn 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
5 140 i n t m = 10 , n = 5 ;
xinty m 10 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xString[ ] y args @heap (?) 160 o b j e t o . A(m) ;
d. r. Sistema 170 o b j e t o . B(m, n ) ;
Operativo 180 objeto .C( ) ;
main
190 }
200 }
En la lı́nea 180 nuevamente se hace una llamada al método C(), por lo que se
marca la pila y se monta su registro de activación. El resultado se puede ver en la
figura 7.20 en la página opuesta.
60 public void B( i n t i , i n t j ) {
xinty k 6 70 int a = i + j;
dirección de 80 C();
regreso: #180 90 }
C() 100 public void C() {
xCualquieray objeto @heap (a=3,b=2) 110 int k = 2 ∗ a;
xinty n 5 120 }
xinty m 10 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 i n t m = 10 , n = 5 ;
xString r sy args @heap (?) 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
d.r. Sistema 160 o b j e t o . A(m) ;
Operativo 170 o b j e t o . B(m, n ) ;
main 180 objeto .C( ) ;
xvoidy main #130 190 }
200 }
queda como se muestra en la figura 7.21, con el PC apuntando a la lı́nea 190 del
código.
10 c l a s s C u a l q u i e r a {
20 private int a = 3 , b = 2;
30 p u b l i c v o i d A( i n t i ) {
40 B( i , a ) ;
50 }
60 p u b l i c v o i d B( i n t i , i n t j ) {
70 int a = i + j ;
xCualquieray objeto @heap (a=3,b=2) 80 C();
xinty n 5 90 }
100 public void C( ) {
xinty m 10 110 int k = 2 ∗ a ;
xString r sy args @heap (?) 120 }
d.r. Sistema 130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Operativo 140 i n t m = 10 , n = 5 ;
main 150 C u a l q u i e r a o b j e t o = new C u a l q u i e r a ( ) ;
xvoidy main #130 160 o b j e t o . A(m) ;
170 o b j e t o . B(m, n ) ;
180 objeto .C( ) ;
190 }
200 }
En la pila se le da lugar a:
Todo lo declarado público en el paquete (o conjunto de programas).
Todas las estructuras de datos de las clases (referencias a ellas).
Apuntadores a todos los métodos miembros de clases.
Los resultados que entregan las funciones.
Los argumentos (parámetros reales) de cada método.
Las variables locales de cada método conforme se van declarando.
7.2 Recursividad
10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
30 i f ( n <= 1 ) {
40 return 1;
50 }
60 else {
70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
80 }
90 }
100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
d.r. Sistema
Operativo 120 int n = Integer . parseInt ( args [ i ] ) ;
main [4] 130 i f ( n <= 0 ) {
xvoidy main #100 140 System . o u t . p r i n t l n ( " Factorial no puede "
xlongy factorial #20 150 + " tomar un argumento negativo " ) ;
d.r. Sistema 160 continue ;
Operativo 170 }
clase Factorial 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
190 }
200 }
210 }
10 c l a s s F a c t o r i a l {
xinty n 4 20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
dirección de 30 i f ( n <= 1 ) {
regreso #110 40 return 1;
factorial(4)
xlongy val regreso
50 }
60 else {
xinty n 4 70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
xinty i 0
80
90 }
}
d.r. Sistema 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Operativo ...
main [4] 210 }
xvoidy main #100
xlongy factorial #20
d.r. Sistema
Operativo
clase Factorial
7.2 Recursividad 358
10 c l a s s F a c t o r i a l {
xinty n 3
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n )
30 i f ( n <= 1 ) {
{
dirección de 40 return 1;
regreso #70 50 }
factorial(3)
xlongy val regreso
60 e l s e {
70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
xinty n 4
dirección de 80 }
regreso #110 90 }
factorial(4)
xlongy val regreso 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
xinty n 4 120 int n = Integer . parseInt ( args [ i ] ) ;
xinty i 0
130
140
i f ( n <= 0 ) {
System . o u t . p r i n t l n ( " Factorial no puede "
d.r. Sistema 150 + " tomar un argumento negativo " ) ;
Operativo 160 continue ;
main [4]
xvoidy main #100
170
180
}
System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
xlongy factorial #20 190 }
d.r. Sistema 200 }
Operativo 210 }
clase Factorial
xinty n 2
dirección de 10 c l a s s F a c t o r i a l {
regreso #70 20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
factorial(2) 30 i f ( n <= 1 ) {
xlongy val regreso 40 return 1;
xinty n 3
50 }
60 e l s e {
dirección de
regreso #70 70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
factorial(3) 80 }
xlongy val regreso 90 }
xinty n 4 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
...
dirección de 210 }
regreso #110
factorial(4)
xlongy val regreso
10 c l a s s F a c t o r i a l {
xinty n 1
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n )
30 i f ( n <= 1 ) {
{
dirección de 40 return 1;
regreso #70
factorial(1) 50 }
xlongy val regreso 60 e l s e {
r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ; \
xinty n 2
70
80 }
dirección de 90 }
regreso #70 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
factorial(2)
xlongy val regreso 110
120
f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
int n = Integer . parseInt ( args [ i ] ) ;
xinty n 3 130 i f ( n <= 0 ) {
dirección de 140 System . o u t . p r i n t l n ( " Factorial no puede "
regreso #70 150 + " tomar un argumento negativo " ) ;
factorial(3) 160 continue ;
xlongy val regreso 170 }
xinty n 4 180
190 }
System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
dirección de 200 }
regreso #110
factorial(4) 210 }
7.2 Recursividad 360
10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
xlongy val regreso 1
30
40
i f ( n <= 1 ) {
return 1;
xinty n 2 50 }
dirección de 60 e l s e {
regreso #70 70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
factorial(2)
xlongy val regreso 80 }
xinty n
90 }
3 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
dirección de 110 f o r ( i n t i = 0 ; i < a r g s . l e n g t h ; i ++) {
regreso #70 120 int n = Integer . parseInt ( args [ i ] ) ;
factorial(3) 130 i f ( n <= 0 ) {
xlongy val regreso 140 System . o u t . p r i n t l n ( " Factorial no puede "
xinty n 4 150
160
+ " tomar un argumento negativo " ) ;
continue ;
dirección de
regreso #110 170 }
factorial(4) 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
190 }
200 }
210 }
xinty n
140 System . o u t . p r i n t l n ( " Factorial no puede "
4 150 + " tomar un argumento negativo " ) ;
xinty i 0 160
170 }
continue ;
d.r. Sistema
Operativo 180 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
main [4] 190 }
200 }
210 }
361 Administración de la memoria durante ejecución
10 c l a s s F a c t o r i a l {
20 p u b l i c s t a t i c long f a c t o r i a l ( i n t n ) {
30 i f ( n <= 1 ) {
xlongy val regreso 6 4 24 40 return 1;
xinty n 4
50 }
xinty i
60 e l s e {
0 70 r e t u r n ( n∗ f a c t o r i a l ( n 1 ) ) ;
d.r. Sistema 80 }
Operativo 90 }
main [4] 100 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
xvoidy main #100 ...
xlongy factorial #20 110 System . o u t . p r i n t l n ( n+"!="+ f a c t o r i a l ( n ) ) ;
d.r. Sistema 120 }
Operativo 130 }
clase Factorial 140 }
7.2 Recursividad 362
Lo que me dice esta estrategia es que si sólo tenemos dos fichas las sabemos
mover “a pie”. Para el caso de que tenga más de dos fichas (n ¡ 2), suponemos
que pudimos mover las n 1 fichas que están en el tope del poste al poste
auxiliar, siguiendo las reglas del juego; después movimos una sola ficha al poste
definitivo, y para terminar movimos las n 1 fichas del poste auxiliar al definitivo.
Como en el caso del cálculo de factorial con recursividad, se entra al método
decrementando la n en 1 hasta que tengamos que mover una sola ficha; en cuanto
7.2 Recursividad 364
la movemos, pasamos a trabajar con el resto de las fichas. Hay que aclarar que
esto funciona porque se van intercambiando los postes 1, 2 y 3. El código (de
manera esquemática) se puede ver en el listado 7.5.
/∗ ∗
∗ R e a l i z a e l m o v i m i e n t o de una f i c h a de un p o s t e a o t r o .
∗ @param p o s t e 1 E l p o s t e d e s d e e l que s e mueven l a s f i c h a s .
∗ @param p o s t e 2 E l p o s t e a l que s e mueve l a f i c h a .
∗/
p u b l i c v o i d mueveUno ( i n t p o s t e 1 , i n t p o s t e 2 ) {
/∗ E s c r i b e e l número de p o s t e c o r r e s p o n d i e n t e
∗ o d i b u j a e l movimiento ∗/
System . o u t . p r i n t l n ( "Del " + p o s t e 1 + " al " + p o s t e 2 ) ;
}
/∗ ∗
∗ Mueve n f i c h a s d e l poste1 a l poste2 , usando e l
∗ poste3 como p o s t e de t r a b a j o .
∗ @param n e l número de f i c h a s a mover .
∗ @param p o s t e 1 e l p o s t e d e s d e e l c u a l s e mueven .
∗ @param p o s t e 2 e l p o s t e d e s t i n o .
∗ @param p o s t e 3 e l p o s t e de t r a b a j o .
∗/
p u b l i c v o i d mueveN ( i n t n , i n t p o s t e 1 , i n t p o s t e 2 , i n t p o s t e 3 ) {
i f ( n == 2 ) {
mueveUno ( p o s t e 1 , p o s t e 3 ) ;
mueveUno ( p o s t e 1 , p o s t e 2 ) ;
mueveUno ( p o s t e 3 , p o s t e 2 ) ;
}
else {
mueveN ( n 1, p o s t e 1 , p o s t e 3 , p o s t e 2 ) ;
mueveUno ( p o s t e 1 , p o s t e 2 ) ;
mueveN ( n 1, p o s t e 3 , p o s t e 2 , p o s t e 1 ) ;
}
}
hasta arriba.
7.2 Recursividad 366
mueveN(4,1,2,3)
¿4 2?
mueveN(3,1,3,2)
¿3 2?
mueveN(2,1,2,3)
¿2 2?
mueveUno(1,3) /* 1 */
mueveUno(1,2) /* 2 */
mueveUno(3,2) /* 3 */
mueveUno(1,3) /* 4 */
mueveN(2,2,3,1)
¿2 2?
mueveUno(2,1) /* 5 */
mueveUno(2,3) /* 6 */
mueveUno(1,3) /* 7 */
mueveUno(1,2) /* 8 */
mueveN(3,3,2,1)
¿3 2?
mueveN(2,3,1,2)
¿2 2?
mueveUno(3,2) /* 9 */
mueveUno(3,1) /* 10 */
mueveUno(2,1) /* 11 */
mueveUno(3,2) /* 12 */
mueveN(2,1,2,3)
¿2 2?
mueveUno(1,3) /* 13 */
mueveUno(1,2) /* 14 */
mueveUno(3,2) /* 15 */
367 Administración de la memoria durante ejecución
/* 1 */
/* 2 */
/* 3 */
/* 4 */
7.2 Recursividad 368
/* 7 */
/* 6 */
/* 5 */
/* 8 */
/* 10 */
/* 11 */ /* 9 */
369 Administración de la memoria durante ejecución
/* 12 */
/* 13 */
/* 14 */ /* 15 */
Como se puede ver del ejercicio con las torres de Hanoi, 4 fichas provocan
15 movimientos. Podrı́amos comprobar que 5 fichas generan 31 movimientos. Es-
to se debe a la recursividad, que se encuentra “escondida” en la simplicidad del
algoritmo. Aunque definitivamente es más fácil expresarlo ası́, que ocupa aproxi-
madamente 10 lı́neas, que dar las reglas con las que se mueven las fichas de dos
en dos.
Entre otros ejemplos que ya no veremos por el momento, donde la solución
recursiva es elegante y mucho más clara que la iterativa se encuentra el recorrido
de árboles, las búsquedas binarias y algunos ordenamientos como el de mezcla
–Mergesort– y el Quicksort.
Con esto damos por terminado una descripción somera sobre cómo se comporta
la memoria durante la ejecución de un programa, en particular la pila de ejecución.
Esta descripción no pretende ser exhaustiva, sino únicamente proporcionar una
idea de cómo identifica la ejecución los puntos de entrada, de regreso y parámetros
a una función.
En el capı́tulo que sigue veremos la aplicación de recursividad a la solución
del problema de la base de datos que hemos estado manejando, máxime cuando
tenemos una definición de listas dada de manera recursiva.
7. Ejercicios 370
Ejercicios
7.1.- Para la serie de Fibonacci, definida para los números naturales de la siguiente
manera:
#
F ibonaccipnq 1 ¤n¤2 Si 1
F ibonaccipn 2q F ibonaccipn 1q Si n ¡ 2
’
(a) ¿Qué escribe la aplicación en la lı́nea 150?
(b) ¿Qué escribe la aplicación en las lı́neas 160 u 170?
371 Administración de la memoria durante ejecución
(c) ¿Qué habrı́a que hacer para que el atributo caja, desde main, tomara el
valor 31? (Hay varias formas de lograrlo).
7.3.- Tenemos el registro del grupo en una lista ligada (puedes usar las definiciones
dadas en al capı́tulo 5). Programa, usando Java, recursivamente el método
que lista el grupo completo. La función de listado está definida de la siguiente
manera:
$ #
'
'
' textoGrupo Ð “Grupo: ” grupo
'
'
' Inicio
'
'
'
' $lista Ð lista ! del grupo
'
'
' '
'
'
' '
'
'
lista vacı́a Regresa la cadena vacı́a
& '
Listado '
& ` $
del grupo '
'
'
'
Procesa lista
' '
&Regresa textoGrupo
'
' '
'
'
'
'
' '
' lista vacı́a + primero de la lista
'
' '
% '
% + Procesa lista con
'
'
' + el resto de la lista
'
%Final
7.6.- Programa recursivamente cómo obtener los distintos patrones de bits con
cuatro posiciones.
7.7.- Programa recursivamente cómo mezclar dos listas de enteros, cada lista or-
denada, de tal manera que el resultado sea una lista ordenada.
7. Ejercicios 372
7.8.- Haz un método que calcule el máximo común divisor (mcd) con la siguiente
fórmula recursiva:
#
mcdpa, bq
b 0
si a
mcdpb, a % bq si a ¡ 0
7.9.- Diseña, implementa y prueba un método recursivo que eleva un entero po-
sitivo dado a una potencia entera positiva. (Nota: tanto la base como la
potencia deben ser parámetros).
/* 1 */
/* 2 */
/* 3 */
/* 4 */
7.2 Recursividad 368
/* 7 */
/* 6 */
/* 5 */
/* 8 */
/* 10 */
/* 11 */ /* 9 */
369 Administración de la memoria durante ejecución
/* 12 */
/* 13 */
/* 14 */ /* 15 */
Como se puede ver del ejercicio con las torres de Hanoi, 4 fichas provocan
15 movimientos. Podrı́amos comprobar que 5 fichas generan 31 movimientos. Es-
to se debe a la recursividad, que se encuentra “escondida” en la simplicidad del
algoritmo. Aunque definitivamente es más fácil expresarlo ası́, que ocupa aproxi-
madamente 10 lı́neas, que dar las reglas con las que se mueven las fichas de dos
en dos.
Entre otros ejemplos que ya no veremos por el momento, donde la solución
recursiva es elegante y mucho más clara que la iterativa se encuentra el recorrido
de árboles, las búsquedas binarias y algunos ordenamientos como el de mezcla
–Mergesort– y el Quicksort.
Con esto damos por terminado una descripción somera sobre cómo se comporta
la memoria durante la ejecución de un programa, en particular la pila de ejecución.
Esta descripción no pretende ser exhaustiva, sino únicamente proporcionar una
idea de cómo identifica la ejecución los puntos de entrada, de regreso y parámetros
a una función.
En el capı́tulo que sigue veremos la aplicación de recursividad a la solución
del problema de la base de datos que hemos estado manejando, máxime cuando
tenemos una definición de listas dada de manera recursiva.
7. Ejercicios 370
Ejercicios
7.1.- Para la serie de Fibonacci, definida para los números naturales de la siguiente
manera:
#
F ibonaccipnq 1 ¤n¤2 Si 1
F ibonaccipn 2q F ibonaccipn 1q Si n ¡ 2
’
(a) ¿Qué escribe la aplicación en la lı́nea 150?
(b) ¿Qué escribe la aplicación en las lı́neas 160 u 170?
371 Administración de la memoria durante ejecución
(c) ¿Qué habrı́a que hacer para que el atributo caja, desde main, tomara el
valor 31? (Hay varias formas de lograrlo).
7.3.- Tenemos el registro del grupo en una lista ligada (puedes usar las definiciones
dadas en al capı́tulo 5). Programa, usando Java, recursivamente el método
que lista el grupo completo. La función de listado está definida de la siguiente
manera:
$ #
'
'
' textoGrupo Ð “Grupo: ” grupo
'
'
' Inicio
'
'
'
' $lista Ð lista ! del grupo
'
'
' '
'
'
' '
'
'
lista vacı́a Regresa la cadena vacı́a
& '
Listado '
& ` $
del grupo '
'
'
'
Procesa lista
' '
&Regresa textoGrupo
'
' '
'
'
'
'
' '
' lista vacı́a + primero de la lista
'
' '
% '
% + Procesa lista con
'
'
' + el resto de la lista
'
%Final
7.6.- Programa recursivamente cómo obtener los distintos patrones de bits con
cuatro posiciones.
7.7.- Programa recursivamente cómo mezclar dos listas de enteros, cada lista or-
denada, de tal manera que el resultado sea una lista ordenada.
7. Ejercicios 372
7.8.- Haz un método que calcule el máximo común divisor (mcd) con la siguiente
fórmula recursiva:
#
mcdpa, bq
b 0
si a
mcdpb, a % bq si a ¡ 0
7.9.- Diseña, implementa y prueba un método recursivo que eleva un entero po-
sitivo dado a una potencia entera positiva. (Nota: tanto la base como la
potencia deben ser parámetros).
Habiendo ya visto arreglos, se nos ocurre que puede resultar más fácil guardar
nuestras listas de cursos en un arreglo, en lugar de tenerlo en una lista ligada.
Todo lo que tenemos que hacer es pensar en cuál es el tamaño máximo de un
grupo y reservar ese número de localidades en un arreglo. La superclase para
el registro con la información del estudiante queda exactamente igual a la que
utilizamos como EstudianteBasico. Como vamos a acomodar al curso en un arreglo
la relación de quién sigue a quién va a estar dada por la posición en el arreglo. Por
el momento ignoraremos el manejo de listas ligadas y no utilizaremos la referencia
al elemento siguiente en los registros –si la requerimos construiremos una subclase
que la contenga–. Para quien lo requiera puede consultar los listados 6.9 y 6.10 en
el capı́tulo sobre herencia –capı́tulo 6–.
La clase ası́ definida puede ser usada, por ejemplo, para cuando queramos
8.1 Base de datos en un arreglo 374
una lista de estudiantes que tengan esta información básica incluida. Un posible
ejemplo se muestra en el listado 8.1. Esta jerarquı́a se puede extender tanto como
queramos. Si pensamos en estudiantes para listas usamos EstudianteBasico para he-
redar, agregando simplemente campos necesarios para mantener la lista, como en
el caso EstudianteLista del Listado 8.1 –omitiremos los comentarios para JavaDoc
para ahorrar espacio–. Esta clase hereda todos los métodos que implementamos
anteriormente.
Código 8.1 Extendiendo la clase EstudianteBasico EstudianteLista
10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
30
40 p u b l i c c l a s s E s t u d i a n t e L i s t a extends E s t u d i a n t e B a s i c o {
50 protected E s t u d i a n t e L i s t a s i g u i e n t e ;
60
70 public EstudianteLista () {
80 }
90
100 public EstudianteLista ( EstudianteBasico est ) {
110 nombre = e s t . getNombre ( ) ;
120 cuenta = e s t . getCuenta ( ) ;
130 carrera = est . getCarrera ();
140 }
150
160 p u b l i c E s t u d i a n t e L i s t a ( S t r i n g nmbre , S t r i n g c t a , i n t c a r r ) {
170 super ( nmbre , c t a , c a r r ) ;
180 }
190 /∗ ∗
200 ∗ O b t i e n e l a r e f e r e n c i a a l s i g u i e n t e en l a l i s t a .
210 ∗ @ r e t u r n e l v a l o r de s i g u i e n t e
220 ∗/
230 public EstudianteLista getSiguiente () {
240 return t h i s . s i g u i e n t e ;
250 }
260
270 /∗ ∗
280 ∗ A c t u a l i z a e l v a l o r de l a r e f e r e n c i a a l s i g u i e n t e de l a l i s t a .
290 ∗ @param a r g S i g u i e n t e v a l o r p a r a a s i g n a r a t h i s . s i g u i e n t e .
300 ∗/
310 public void s e t S i g u i e n t e ( E s t u d i a n t e L i s t a a r g S i g u i e n t e ) {
320 this . siguiente = argSiguiente ;
330 }
340 }
10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
30 import u t i l e s . ∗ ;
130 p u b l i c c l a s s E s t u d i a n t e C a l i f s extends E s t u d i a n t e B a s i c o {
140 protected f l o a t [ ] c a l i f s ; // Para g u a r d a r c a l i f i c a c i o n e s
150 protected f i n a l int numCalifs ;
160 protected s t a t i c f i n a l i n t NUMCALIFS = 1 0 ;
210 public Es tu d i a n t eC a l i f s () {
220 /∗ En a u t o m á t i c o i n v o c a a l c o n s t r u c t o r s i n p a r á m e t r o s que s e
230 ∗ d e c l a r ó en l a s u p e r c l a s e . ∗/
240 n u m C a l i f s = NUMCALIFS ;
250 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
260 }
270
280 public E s t u d i a n t e C a l i f s ( EstudianteBasico est , int numCalifs ) {
290 super ( e s t ) ;
300 this . numCalifs = numCalifs ;
310 c a l i f s = new f l o a t [ n u m C a l i f s ] ;
320 }
semejanzas:
En ambas estructuras existe una noción de orden entre los elementos de la
estructura: podemos determinar cuál elemento va antes y cuál después. Deci-
mos entonces que ambas estructuras son lineales porque podemos “formar”
a los elementos en una lı́nea. En el caso de los arreglos el orden está dado
por el ı́ndice y en el caso de las listas está dado por la posición relativa entre
los elementos.
Todos los elementos de una lista o de un arreglo son del mismo tipo. Decimos
entonces que ambas estructuras son homogéneas.
En cuanto a las diferencias mencionamos las siguientes:
Las listas pueden cambiar de tamaño durante la ejecución, mientras que
los arreglos, una vez definido su tamaño, éste ya no puede cambiar. Las
listas son estructuras dinámicas mientras que los arreglos son estructuras
estáticas.
El acceso al elemento de una lista se lleva a cabo recorriendo cada uno de
los elementos que están antes que el que buscamos; esto es, es un acceso
secuencial ; el acceso a un elemento de un arreglo es mediante un ı́ndice, o
sea acceso directo.
Dependiendo de qué tipo de datos tengamos y cuáles sean las operaciones a
realizar sobre la estructura, podremos elegir entre listas o arreglos para nuestras
estructuras de datos. Esta decisión deberá estar justificada de alguna manera.
Para construir la clase que maneja el arreglo, lo primero que debemos consi-
derar es que en lugar de una cabeza de lista deberemos tener el arreglo, definido
de un cierto tamaño, que corresponderá al máximo número de elementos que es-
peramos. Nuestros algoritmos son exactamente igual, excepto que interpretamos
de distinta manera “toma el siguiente” o “colócate al principio”. En el caso de
que los registros estén en un arreglo “colócate al principio” se interpreta como
“inicializa un ı́ndice en 0”; y “toma el siguiente” se interpreta como “incrementa
en uno a la variable empleada como ı́ndice”. Con esta representación es fácil ob-
tener el anterior, ya que únicamente se decrementa el ı́ndice en 1, si es que existe
anterior. Además, en todo momento tenemos que tener cuidado de no tratar de
tomar elementos más allá del fin del arreglo.
En una primera intención, tomamos directamente a la clase EstudianteCalifs
para colocarlos en la lista guardada en un arreglo. Sin embargo, para saber quién
es el siguiente –ocupa la siguiente posición en el arreglo– o quién es el anterior
–ocupa la posición anterior en el arreglo– vamos a anotar en cada registro la
posición en la que se encuentran. Con este cambio generamos una nueva clase,
EstudianteVector, que hereda de EstudianteCalifs y que tiene un atributo entero.
La implementación de esta clase se encuentra en el listado 8.3 en la siguiente
página –nuevamente omitiremos los comentarios de JavaDoc para hacer eficiente
8.1 Base de datos en un arreglo 378
Código 8.3 Clase para los elementos del curso (en un arreglo) EstudianteVector
10 package C u r s o V e c t o r ;
20 import u t i l e s . ∗ ;
30 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
40
50 p u b l i c c l a s s E s t u d i a n t e V e c t o r extends E s t u d i a n t e C a l i f s {
60 p r i v a t e i n t p o s ; // Para r e c u p e r a r s u l u g a r en e l a r r e g l o
260 p u b l i c E s t u d i a n t e V e c t o r ( S t r i n g nmbre , S t r i n g cn ta , i n t c r r e r a ,
270 int numCalifs ) {
280 super ( nmbre , c n ta , c r r e r a , n u m C a l i f s ) ;
290 p o s = 1;
300 }
Veamos cómo queda con estos cambios.1 Dado un arreglo es fácil saber cuál
1
Se recomienda referirse a los diagramas de Warnier-Orr donde se dieron los algoritmos en
su momento.
379 Ordenamientos usando estructuras de datos
10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
120 p u b l i c i n t e r f a c e S e r v i c i o s C u r s o {
180 S t r i n g getGrupo ( ) ;
250 // S t r i n g daNombre ( i n t c u a l ) ;
260 S t r i n g daNombre ( E s t u d i a n t e B a s i c o c u a l ) ;
330 // S t r i n g d a C a r r e r a ( i n t c u a l ) ;
340 String daCarrera ( EstudianteBasico cual ) ;
410 // S t r i n g daCuenta ( i n t c u a l ) ;
420 S t r i n g daCuenta ( E s t u d i a n t e B a s i c o c u a l ) ;
510 // Es nuevo
520 E s t u d i a n t e B a s i c o l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ,
530 EstudianteBasico desde ) ;
640 E s t u d i a n t e B a s i c o l o c a l i z a A l u m n o ( S t r i n g cadena , i n t campo ) ;
730 v o i d agregaAlumno ( S t r i n g n , S t r i n g c t a , i n t ca ) ;
790 void eliminaAlumno ( E s t u d i a n t e B a s i c o c u a l ) ;
860 S t r i n g dameLista ( ) ;
920 S t r i n g losQueCazanCon ( S t r i n g q u i e n , i n t campo ) ;
930 } // S e r v i c i o s C u r s o
Con esta interfaz en mente y usando la extensión del método toString para
mostrar el contenido de los objetos, procedemos a implementar una clase para
el curso, guardados los estudiantes en un arreglo. El código se encuentra en el
listado 8.5 en la página 381. Iremos introduciendo los métodos conforme los dis-
cutamos. Por lo pronto presentamos aquellos que no requieren mayor explicación.
8.1 Base de datos en un arreglo 380
10 package C u r s o V e c t o r ;
20 import H e r e n c i a . E s t u d i a n t e B a s i c o ;
90 p u b l i c c l a s s C u r s o E n V e c t o r implements S e r v i c i o s C u r s o {
100 protected s t a t i c f i n a l i n t
110 NOMBRE = 1 , // I d d e l campo p a r a nombre
120 CARRERA = 2 , // I d d e l campo p a r a c a r r e r a
130 CUENTA = 3 ; // I d d e l campo p a r a numero de c u e n t a
140 p r i v a t e s t a t i c f i n a l i n t TAM GRUPO = 4 ; // Para l a c l a s e d e l g r u p o
150
160 p r i v a t e i n t NUM CALIFS = 1 0 ; // Núm . de c a l i f i c a c i o n e s p o r alumno
170 p r i v a t e f i n a l i n t MAXREG = 1 0 ; // Núm . de r e g i s t r o s p o r o m i s i ó n
180 p r i v a t e E s t u d i a n t e B a s i c o [ ] l i s t a ; // L i s t a de alumnos
190 private S t r i n g grupo ; // Nombre d e l g r u p o
200 p r i v a t e i n t numRegs = 0 ; // Núm . r e g i s t r o s v i v o s
400 p u b l i c C u r s o E n V e c t o r ( S t r i n g gr , i n t c u a n t o s , i n t n u m C a l i f s ) {
410 NUM CALIFS = n u m C a l i f s ;
420 grupo = gr ;
430 numRegs = 0 ;
440 l i s t a = new E s t u d i a n t e V e c t o r [ c u a n t o s ] ;
450 }
Hay algunas operaciones básicas que vamos a necesitar al trabajar con arreglos.
Por ejemplo, para agregar a un elemento en medio de los elementos del arreglo
(o al principio) necesitamos recorrer hacia abajo a todos los elementos que se
encuentren a partir de la posición que queremos que ocupe el nuevo elemento.
Esto lo tendremos que hacer si queremos agregar a los elementos y mantenerlos
en orden conforme los vamos agregando.
Similarmente, si queremos eliminar a alguno de los registros del arreglo, tene-
mos que recorrer a los que estén más allá del espacio que se desocupa para que se
ocupe el lugar que se acaba de desocupar. En ambos casos tenemos que recorrer
a los elementos uno por uno y deberemos tener mucho cuidado en el orden en
que recorramos a los elementos. Al recorrer hacia abajo (para hacer lugar) debe-
remos recorrer desde el final del arreglo hacia la primera posición que se desea
mover. Si no se hace en este orden se tendrá como resultado el valor del primer
registro que se desea mover copiado a todos los registros abajo de éste. El método
para recorrer hacia abajo se encuentra en el listado 8.6 en la siguiente página.
El método nos tiene que regresar si pudo o no pudo recorrer a los elementos. En
el caso de que no haya suficiente lugar hacia abajo, nos responderá falso, y nos
8.1 Base de datos en un arreglo 382
Con este método es fácil programar los métodos que agregan al principio o al
final de la lista; se muestran en el listado 8.9.
1610 p u b l i c v o i d a g r e g a E s t F i n a l ( E s t u d i a n t e B a s i c o nuevo ) {
1620 i f ( numRegs >= l i s t a . l e n g t h ) {
1630 EstudianteBasico [ ] nuevaLista ;
1640 nuevaLista = copiaLista ( ) ;
1650 l i s t a = nuevaLista ;
1660 }
1670 l i s t a [ numRegs++] = nuevo ;
1680 }
1770 p u b l i c v o i d a g r e g a E s t ( E s t u d i a n t e B a s i c o nuevo ) {
1780 i f ( numRegs >= l i s t a . l e n g t h ) {
1790 EstudianteBasico [ ] nuevaLista ;
1800 nuevaLista = copiaLista ( ) ;
1810 l i s t a = nuevaLista ;
1820 }
1830 i f (! r e c o r r e (0 , 1)) {
1840 System . o u t . p r i n t l n ( "No se pudo insertar " ) ;
1850 return ;
1860 }
1870 l i s t a [ 0 ] = nuevo ;
1880 }
cadenas. Recordemos que el método compareTo de la clase String nos sirve para
saber la relación entre dos cadenas, de la siguiente forma:
$
&1 Si s1 s2
'
'
s1.compareTo(String s2) 0 Si s1 == s2
'
'
% 1 Si s1 ¡ s2
Para agregar un registro donde nos proporcionan los datos individuales del
estudiante, simplemente armamos el registro e invocamos al método que agrega
un registro ya armado, como se puede ver en el listado 8.11 en la página opuesta.
387 Ordenamientos usando estructuras de datos
Código 8.11 Inserción de registros en lista ordenada con datos individuales CursoEnVector
Podemos salir de la iteración porque se acaben los registros vivos –condición del
for en la lı́nea 2680– o porque se encuentre a un elemento mayor lexicográficamente
–el enunciado break de la lı́nea 2800–. En el primer caso habremos salido porque
el ı́ndice llegó al número de registros almacenados (actual == numRegs), en cuyo
caso simplemente colocamos al nuevo registro en el primer lugar sin ocupar del
arreglo, ya que no hay ningún nombre registrado que sea lexicográficamente mayor.
No hay peligro en esto pues al entrar al método verificamos que todavı́a hubiera
lugares disponibles y si no los habı́a, crecimos el espacio –lı́neas 2600 a 2640–.
En el caso de que haya encontrado un lugar entre dos elementos del arreglo,
tenemos que recorrer a todos los que son mayores que él para hacer lugar. Es-
to se hace en la lı́nea 2770, donde de paso preguntamos si lo pudimos hacer –
seguramente sı́ porque ya habı́amos verificado que hubiera lugar. Una vez reco-
rridos los elementos del arreglo, colocamos el nuevo elemento en el lugar que se
desocupó gracias al corrimiento, y avisamos que todo estuvo bien – lı́neas 2900 y
2920 – no sin antes incrementar el contador de registros.
El método hace lo siguiente: Ubica la posición del registro en el arreglo –lı́nea 1900
del listado 8.12–. Una vez hecho esto –recordemos que agregamos un atributo que
registre esto–, se procede a “desaparecerlo”, recorriendo a los registros que están
después que él un lugar a la izquierda, encimándose en el registro que se está qui-
tando; esto se hace con la llamada a regresa(actual,1) en la lı́nea 1950 del mismo
listado. Al terminar de recorrer a los registros hacia la izquierda, se decrementa
el contador de registros numRegs.
El método que busca una subcadena en alguno de los campos en el arreglo
cambia la forma en que nos colocamos al principio: colocarse al principio ahora
implica poner al ı́ndice que vamos a usar para recorrerlo en 0 – lı́nea 2060 del
listado 8.13 en la página opuesta – mientras que tomar el siguiente quiere decir
incrementar en 1 el ı́ndice que se está usando para recorrer el arreglo – lı́nea 2090
del listado 8.13 en la página opuesta.
389 Ordenamientos usando estructuras de datos
Código 8.13 Búsqueda de una subcadena en algún campo del arreglo (CursoEnVector)
1990 /∗ ∗ Busca a l r e g i s t r o que c o n t e n g a a l a s u b c a d e n a .
2000 ∗ @param i n t c u a l C u a l e s e l campo que s e va a c o m p a r a r .
2010 ∗ @param S t r i n g s u b c a d La c a d e n a que s e e s t á b u s c a n d o .
2020 ∗ @ r e t u r n s i n t E l r e g i s t r o d e s e a d o o 1. ∗/
2030 public E s t u d i a n t e B a s i c o buscaSubcad ( i n t cual , S t r i n g subcad ) {
2040 int actual ;
2050 subcad = subcad . trim ( ) . toLowerCase ( ) ;
2060 actual = 0;
2070 w h i l e ( a c t u a l < numRegs && ( l i s t a [ a c t u a l ] . daCampo ( c u a l ) .
2080 i n d e x O f ( s u b c a d . t o L o w e r C a s e ( ) ) ) == 1)
2090 a c t u a l ++;
2100 i f ( a c t u a l < numRegs )
2110 return l i s t a [ a c t u a l ] ;
2120 else
2130 return n u l l ;
2140 }
le tiene que pasar como parámetro y qué espera como resultado. Excepto por
los métodos que agregan y quitan estudiantes, que los volvimos booleanos para
que informen si pudieron o no, todos los demás métodos mantienen la firma que
tenı́an en la implementación con listas ligadas. Vale la pena decir que podrı́amos
modificar los métodos de las listas ligadas a que también contestaran si pudieron
o no, excepto que en el caso de las listas ligadas siempre podrı́an.
Nos falta revisar nada más dos métodos: el que lista todo el contenido de la base
de datos y el que lista solamente los que cazan con cierto criterio. Para el primer
método nuevamente se aplica la transformación de que colocarse al principio de la
lista implica poner al ı́ndice que se va a usar para recorrerla en 0 – lı́nea 2240 en el
listado 8.14. Nuevamente nos movemos por los registros incrementando el ı́ndice
en 1, y verificamos al salir de la iteración si encontramos lo que buscábamos o no.
En el caso del método que lista a los que cazan con cierto criterio – listado 8.15
en la página opuesta – nuevamente se recorre el arreglo de la manera que ya vimos,
excepto que cada uno que contiene a la subcadena es listado. Para saber si se listó o
no a alguno, se cuentan los que se van listando – lı́nea 2390 en el listado 8.15 en la
página opuesta. Si no se encontró ningún registro que satisficiera las condiciones
dadas, se da un mensaje de error manifestándolo.
391 Ordenamientos usando estructuras de datos
Código 8.15 Listando los que cumplan con algún criterio (CursoEnVector)
2320 /∗ ∗
2330 ∗ Imprime l o s r e g i s t r o s que c a z a n con un c i e r t o p a t r ó n .
2340 ∗ @param i n t c u a l Con c u á l campo s e d e s e a c o m p a r a r .
2350 ∗ @param S t r i n g s u b c a d Con e l que queremos que c a c e .
2360 ∗ @ r e t u r n <code>S t r i n g </code> l a l i s t a con l o s que c a z a n .
2370 ∗/
2380 p u b l i c S t r i n g losQueCazanCon ( i n t c u a l , S t r i n g s u b c a d ) {
2390 int i = 0;
2400 subcad = subcad . toLowerCase ( ) ;
2410 S t r i n g s L i s t a = "" ;
2420 int actual ;
2430
2440 /∗ ∗ R e c o r r e m o s b u s c a n d o e l r e g i s t r o ∗/
2450 f o r ( a c t u a l = 0 ; a c t u a l < numRegs ; a c t u a l ++) {
2460 i f ( l i s t a [ a c t u a l ] . daCampo ( c u a l ) . i n d e x O f ( s u b c a d ) !=
2470 1) {
2480 i ++;
2490 s L i s t a += l i s t a [ a c t u a l ] . d a R e g i s t r o ( ) ) ;
2500 }
2510 }
2520 /∗ ∗ S i no s e e n c o n t r ó n i n g ú n r e g i s t r o ∗/
2530 i f ( i == 0 ) {
2540 s L i s t a = "No se encontró ningún registro " +
2550 "que cazara " ) ;
2560 }
2570 }
Tenemos ya una clase que maneja a la base de datos en una lista ligada
(ListaCurso). Podemos modificar levemente ese programa para beneficiarnos de
la herencia y hacer que Estudiante herede de la clase EstudianteBasico, y de esa
manera reutilizar directamente el código que ya tenemos para EstudianteBasico.
Todo lo que tenemos que hacer es agregarle los campos que EstudianteBasico no
tiene y los métodos de acceso y manipulación para esos campos. La programación
de la clase utilizando herencia se puede observar en el listado 8.16 en la siguiente
página.
8.2 Mantenimiento del orden con listas ligadas 392
Código 8.16 Definición de la clase Estudiante para los registros (Estudiante) 1/3
10 import j a v a . u t i l . S c a n n e r ;
20 /∗ ∗
30 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula l a l i s t a
40 ∗ de un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s n o r m a l e s de una
50 ∗ b a s e de d a t o s y f u n c i o n a m e d i a n t e un Menú
60 ∗/
70 c l a s s E s t u d i a n t e extends E s t u d i a n t e B a s i c o {
80 protected E s t u d i a n t e s i g u i e n t e ;
90 protected S t r i n g c l a v e ;
100 p u b l i c s t a t i c f i n a l i n t CLAVE = 4 ;
110 /∗ ∗ C o n s t r u c t o r s i n p a r á m e t r o s . ∗/
120 public Estudiante () {
130 super ( ) ;
140 clave = null ;
150 siguiente = null ;
160 }
170 /∗ ∗
180 ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
190 ∗ Los campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas , m i e n t r a s
200 ∗ que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́ p o r punto
210 ∗ y coma .
220 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
230 ∗ cada uno de l o s campos que s e van a l l e n a r .
240 ∗ @ r e t u r n E s t u d i a n t e una r e f e r e n c i a a una l i s t a
250 ∗/
260 p u b l i c E s t u d i a n t e ( S t r i n g nmbre , S t r i n g cn ta , S t r i n g c l v e ,
270 String crrera ) {
280 super ( nmbre , c n ta , c r r e r a ) ;
290 clave = clve . trim ( ) ;
300 siguiente = null ;
310 }
320 /∗ ∗
330 ∗ R e g r e s a e l c o n t e n i d o d e l campo c l a v e .
340 ∗/
350 public String getClave () {
360 return c l a v e ;
370 }
380 /∗ ∗
390 ∗ A c t u a l i z a e l campo c l a v e con e l v a l o r que p a s a como
400 ∗ p a r á m e t r o .
410 ∗/
420 public void s e t C l a v e ( S t r i n g c l v e ) {
430 clave = clve ;
440 }
393 Ordenamientos usando estructuras de datos
Hay que notar que lo que programamos como de acceso privado cuando no
tomábamos en consideración la herencia, ahora se convierte en acceso protegido,
para poder extender estas clases.
Algunos de los métodos que enunciamos en esta clase son, simplemente, méto-
dos nuevos para los campos nuevos. Tal es el caso de los que tienen que ver con
clave y siguiente. El método getCampo se redefine en esta clase, ya que ahora tiene
que considerar más posibilidades. También el método getRegistro es una redefini-
ción, aunque usa a la definición de la superclase para que haga lo que correspondı́a
a la superclase.
Los constructores también son interesantes. Cada uno de los constructores,
tanto el que tiene parámetros como el que no, llaman al correspondiente cons-
tructor de la superclase, para que inicialice los campos que tiene en común con la
superclase.
La palabra super se está utilizando de dos maneras distintas. Una de ellas,
en el constructor, estamos llamando al constructor de la superclase usando una
notación con argumentos. En cambio, en el método getRegistro se usa igual que
cualquier otro objeto, con la notación punto. En este segundo caso nos referimos
al “súper-objeto” de this, a aquél definido por la superclase.
lista
nuevo Alberto ∅
lista
nuevo Ricardo ∅
Debemos insistir en que se debe tener cuidado el orden en que se cambian las
referencias. Las referencias que vamos a modificar son la de nuevo y la siguiente
en anterior, que es la misma que tenemos almacenada en actual. Por ello, el orden
para cambiar las referencias podrı́a haber sido
1380 a n t e r i o r . p o n S i g u i e n t e ( nuevo ) ;
1390 nuevo . p o n S i g u i e n t e ( a c t u a l ) ;
8.3 *Ordenamiento usando árboles 398
Lo que debe quedar claro es que una vez modificado anterior.siguiente, esta refe-
rencia ya no se puede usar para colocarla en nuevo.siguiente.
$
'
'
&Un nodoÀque no tiene hijos
Un árbol n-ario es
'
'
% Un nodo que tiene como hijos a n árboles
raı́z
Info
......
Árbol Árbol
Árbol
En estos momentos revisaremos únicamente a los árboles binarios, por ser éstos
un mecanismo ideal para organizar a un conjunto de datos de manera ordenada.
Un árbol binario, entonces, es un árbol donde el máximo número de hijos para
cada nodo es dos. Si lo utilizamos para organizar cadenas, podemos pensar que
dado un árbol, cada nodo contiene una cierta cadena. Todas las cadenas que se
encuentran en el subárbol izquierdo son menores a la cadena que se encuentra en
la raı́z. Todas las cadenas que se encuentran en el subárbol derecho, son mayores
8.3 *Ordenamiento usando árboles 400
“H”
∅ “A” “P”
Cuando todos los nodos, excepto por las hojas del último nivel, tienen exac-
tamente el mismo número de hijos, decimos que el árbol está completo. Si la pro-
fundidad del subárbol izquierdo es la misma que la del subárbol derecho, decimos
que el árbol está equilibrado 2 . El árbol del esquema anterior no está ni completo
ni equilibrado.
Para el caso que nos ocupa, la clase correspondientes a cada Estudiante vuelve
a extender a la clase EstudianteBasico, agregando las referencias para el subárbol
izquierdo y derecho, y los métodos de acceso y manipulación de estos campos. La
programación se puede ver en el listado 8.18 en la página opuesta.
Como se ve de la declaración del registro ArbolEstudiante, tenemos una es-
tructura recursiva, donde un registro de estudiante es la información, con dos
referencias a registros de estudiantes.
2
En inglés, balanced
401 Ordenamientos usando estructuras de datos
∅ “A”
∅ “B”
∅ “D”
∅ “E”
∅ “F”
∅ “G”
∅ “H”
∅ “M”
∅ “N”
∅ “P”
∅ “R”
∅ “T”
∅ “Y” ∅
8.3.3. Inserción
Del algoritmo en la figura 8.9 en la página opuesta podemos ver que únicamente
podemos agregar un registro cuando le encontramos lugar como una hoja. Lo
que tenemos que hacer es ir bajando por la izquierda o la derecha del árbol,
dependiendo del resultado de comparar al registro que se desea agregar con el que
se encuentra en la raı́z del subárbol en turno. El algoritmo se traduce bastante
directamente a código.
405 Ordenamientos usando estructuras de datos
raı́z
“Manuel”
∅ “Anita” ∅ ∅ “Octavio” ∅
Como podemos ver en este esquema, para listar el contenido de los nodos en
orden tenemos que recorrer el árbol de la siguiente manera:
En nuestro caso, el proceso del nodo raı́z de ese subárbol en particular consiste
en escribirlo. El método público que muestra toda la lista queda con la firma como
la tenı́a en las otras dos versiones que hicimos, y programamos un método privado
que se encargue ya propiamente del recorrido recursivo, cuya firma será
private void l i s t a A r b o l ( A r b o l E s t u d i a n t e r a i z )
Quisiéramos guardar el contenido del árbol en disco, para la próxima vez em-
pezar a partir de lo que ya tenemos. Si lo guardamos en orden, cuando lo volvamos
a cargar va a producir un árbol degenerado, pues ya vimos que lo peor que nos
puede pasar cuando estamos cargando un árbol binario es que los datos vengan en
orden (ya sea ascendente o descendente). Por ello, tal vez serı́a más conveniente
recorrer el árbol de alguna otra manera. Se nos ocurre que si lo recorremos en
preorden, vamos a producir una distribución adecuada para cuando volvamos a
cargar el directorio. Esta distribución va a ser equivalente a la original, por lo que
estamos introduciendo un cierto factor aleatorio. El algoritmo es igual de sencillo
que el que recorre en orden simétrico y se muestra en la figura 8.12 en la siguiente
página.
8.3 *Ordenamiento usando árboles 410
8.3.6. Búsquedas
$ !
'
'
' subárbol vacı́o regresa nulo
'
'
' À
'
'
'
'
' !
'
'
' cadena en raı́z regresa la raı́z
'
'
' À
'
'
'
'
' #
'
'
&Hay árbol izquierdo donde Ð Busca cadena en
Busca cadena
en subárbol ' subárbol izquierdo
'
'
'
'
'
' !
'
'
'
' donde nulo Regresa donde
'
'
' À
'
'
'
'
' #
'
'
' Regresa búsqueda en
'
'
%Hay subárbol derecho subárbol derecho
Código 8.24 Listado de registros que contienen a una subcadena (ArbolOrden) 1/2
2270 /∗ ∗ Imprime l o s r e g i s t r o s que c a z a n con un c i e r t o p a t r ó n .
2280 ∗ @param i n t c u a l Con c u á l campo s e d e s e a c o m p a r a r .
2290 ∗ @param S t r i n g s u b c a d Con e l que queremos que c a c e .
2300 ∗/
2310 p u b l i c v o i d losQueCazanCon ( i n t c u a l , S t r i n g s u b c a d ) {
2320 subcad = subcad . trim ( ) . toLowerCase ( ) ;
2330 int cuantos = 0;
2340 i f ( r a i z == n u l l )
2350 System . o u t . p r i n t l n ( "No hay registros en la base de"
2360 + " datos " ) ;
2370 else
2380 c u a n t o s = b u s c a Ca z a n ( cons , c u a l , subcad , r a i z ) ;
2390 i f ( c u a n t o s == 0 )
2400 System . o u t . p r i n t l n ( "No hay registros que cacen ." ) ;
2410 }
415 Ordenamientos usando estructuras de datos
Como dijimos, una vez que se tiene al padre de una hoja, todo lo que hay que
hacer es identificar si el nodo es hijo izquierdo o derecho y poner el apuntador
correspondiente en null.
Resuelta la eliminación de una hoja, pasemos a ver la parte más complicada,
que es la eliminación de un nodo intermedio. Veamos, por ejemplo, el árbol de
la figura 8.7 en la página 400 y supongamos que deseamos eliminar el nodo eti-
quetado con “E”. ¿Cómo reacomodamos el árbol de tal manera que se conserve
el orden correcto? La respuesta es que tenemos que intercambiar a ese nodo por
el nodo menor de su subárbol derecho. ¿Por qué? Si colocamos al nodo menor
del subárbol derecho en lugar del que deseamos eliminar, se sigue cumpliendo que
todos los que estén en el subárbol izquierdo son menores que él, mientras que
todos los que estén en el subárbol derecho son mayores o iguales que él:
Para localizar el elemento menor del subárbol derecho simplemente bajamos a
la raı́z del subárbol derecho y de ahı́ en adelante seguimos bajando por las ramas
izquierdas hasta que ya no haya ramas izquierdas.El algoritmo para encontrar el
elemento menor de un subárbol se encuentra en la figura 8.16 en la página opuesta.
La programación de este método se muestra en el listado 8.26.
417 Ordenamientos usando estructuras de datos
$ $ !
'
' '
'
' El nodo es hoja
'
'
' '
' À
Elimina a la raı́z
'
' '
'
'
'
'
' '
' #
'
' '
'
'
'
'
' '
'
El nodo tiene sólo Pon al subárbol derecho
'
' '
'
'
'
'
' '
'
subárbol derecho en la raı́z
'
' '
' À
'
' '
& #
'
'
'
'
'
'
nodo == raı́z El nodo tiene sólo
' Pon al subárbol izquierdo
'
' '
'
'
'
'
' '
' subárbol izquierdo en la raı́z
'
' '
'
' À
'
'
' '
'
'
' '
' $
'
' '
'
' '
'
'
'
' '
' &Localiza menor en subárbol derecho
'
' '
'
'
El nodo tiene
'
'
' '
' ' Intercambia a menor y nodo
'
' % ambos hijos '
%
'
& Elimina a quien quedó en menor
Elimina À
nodo ' ' $ !
'
'
' '
'
' '
'
'
El nodo es hoja Anula el apuntador del padre
'
' '
' À
'
'
' '
'
'
' '
'
' #
'
'
' '
'
'
' '
'
'
El nodo tiene sólo Sube al subárbol derecho
'
'
' '
'
'
' '
'
'
subárbol derecho
À
al lugar del nodo
'
'
' '
'
'
' & #
'
'
' nodo == raı́z El nodo tiene sólo Sube al subárbol izquierdo
'
'
' '
'
'
' '
'
'
'
'
' '
'
subárbol izquierdo
À
al lugar del nodo
'
' '
'
'
'
'
' '
' $
'
' '
'
' '
'
' '
' '
&Localiza menor en subárbol derecho
'
'
' '
' El nodo tiene
'
' '
'
' Intercambia a menor y nodo
'
% '
% ambos hijos '
'
% Elimina a quien quedó en menor
8.3 *Ordenamiento usando árboles 420
Por último, el mecanismo para modificar algún registro no puede ser, sim-
plemente, modificar la información, pues pudiera ser que la llave cambiara y el
registro quedara fuera de orden. La estrategia que vamos a utilizar para modificar
la información de un registro es primero borrarlo y luego reinsertarlo, para evitar
que se modifique la llave y se desacomode el árbol.
Ejercicios
8.2.- Tienes una lista de enteros que está ordenada y almacenada en lugares con-
tiguos de un arreglo. Diseña, programa y prueba un método para insertar a
un nuevo entero en el lugar que le corresponde –dado un entero duplicado, el
último que ingresa va después del primero que ingresa, pero sı́ debe aparecer
tantas veces como se presente–.
8.3.- Para el ejercicio anterior, inserta código en tu método que cuente el número
de movimientos que se tienen que hacer cada vez que se ingresa un elemento.
8.5.- Diseña, programa y prueba un método que mezcle los enteros de dos arreglos
que vienen en orden cada uno, para obtener un único arreglo que incluya a
ambas listas en orden.
8.6.- Diseña, programa y prueba un método que incorpore a una lista de enteros
que se encuentran en un arreglo a otra lista de enteros que se encuentran en
otro arreglo. Se debe considerar el agrandar al arreglo anfitrión. Incorpora
al método el número de movimientos y copias que debes hacer.
8. Ejercicios 424
8.7.- Lo mismo que el ejercicio anterior, excepto que los enteros se encuentran en
una lista ligada. Incorpora al método el número de movimientos y copias
que debes hacer.
8.8.- Cuando decimos que los arreglos y las listas ligadas corresponden a la misma
estructura de datos, ¿a qué se refiere esta afirmación?
8.10.- Si tenemos una lista de enteros cuyas magnitudes son relativamente pe-
queñas (por ejemplo, números entre 100 y 100, podrı́amos usar arreglos
sin exigir que todos los elementos estén en posiciones consecutivas. Por ejem-
plo, a cada entero le podemos asignar la posición i 100 y acomodarı́amos
a todos los enteros. Dado este esquema:
(a) ¿Cómo manejas los números repetidos?
(b) ¿Cómo sabes cuántos enteros están realmente en la lista?
(c) ¿Qué haces si deseas alguna información adicional al entero?
Nota: A este tipo de almacenamiento se le conoce como función de disper-
sión, donde a partir de los datos se calcula una posición en la que se
va a acomodar el registro.
Manejo de errores
en ejecución 9
9.1 Tipos de errores
Todos hemos padecido en algún momento errores de ejecución. Java tiene me-
canismos muy poderosos para detectar errores de ejecución de todo tipo, a los que
llama excepciones. Por ejemplo, si se trata de usar una referencia nula, Java ter-
minará (abortará) el programa con un mensaje de error. Generalmente el mensaje
tiene la forma:
Exception in thread "main" java.lang.NullPointerException
at MenuLista.main(MenuLista.java:151)
En el primer renglón del mensaje Java nos dice el tipo de excepción que causó que
el programa “abortara”, que en este caso es NullPointerException, y a partir del
segundo renglón aparece la “historia” de la ejecución del programa, esto es, los
registros de activación montados en la pila de ejecución en el momento en que
sucede el error. Hay muchos errores a los que Java va a reaccionar de esta manera,
como por ejemplo usar como ı́ndice a un arreglo un entero menor que cero o ma-
yor o igual al tamaño declarado (ArrayIndexOutOfBoundsException) o una división
entre 0 (ArithmeticException).
9.1 Tipos de errores 426
10 package e x c e p c i o n e s ;
20 import j a v a . u t i l . S c a n n e r ;
30 import j a v a . u t i l . I n p u t M i s m a t c h E x c e p t i o n ;
40
50 public class ExcepcionesRuntimeSimples {
60 /∗ ∗ E j e m p l i f i c a A r i t h m e t i c E x c e p t i o n ∗/
70 p r i v a t e s t a t i c double c a l c u l a ( i n t k ) {
80 i n t n = 100 / k ;
90 double x = 1000 / k ;
100 r e t u r n Math . max ( n , x ) ;
110 }
120 /∗ ∗ E j e m p l i f i c a A r r a y S t o r e E x c e p t i o n ∗/
130 private s t a t i c void guardaObjeto ( Object [ ] A, i n t i , Object o b j e t o ){
140 A[ i ] = objeto ;
150 }
160 /∗ ∗ E j e m p l i f i c a C l a s s C a s t E x c e p t i o n ∗/
170 private s t a t i c void i n t e r p r e t a O b j e t o ( I n t e g e r [ ] A, i n t i ,
180 Object objeto ) {
190 A[ i ] = ( I n t e g e r ) objeto ;
200 }
210 /∗ ∗ E j e m p l i f i c a I l l e g a l A r g u m e n t E x c e p t i o n ∗/
220 p r i v a t e s t a t i c double r a i z C u a r t a ( double x ) {
230 r e t u r n Math . s q r t ( Math . s q r t ( x ) ) ;
240 }
250 /∗ ∗ E j e m p l i f i c a I n d e x O u t O f B o u n d s E x c e p t i o n ∗/
260 p r i v a t e s t a t i c i n t maximo ( i n t [ ] v a l o r e s , i n t maxInd ) {
270 i n t maxi = v a l o r e s [ 0 ] ;
280 f o r ( i n t i =0; i <= maxInd ; i ++) {
290 try {
300 i f ( maxi < v a l o r e s [ i ] ) {
310 maxi = v a l o r e s [ i ] ;
320 }
330 } catch ( A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n e ) {
340 throw new A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n
350 ( " tamanho del arreglo :"
360 + v a l o r e s . l e n g t h +"\n"
370 + " valor de i="+i ) ;
380 }
390 }
400 r e t u r n maxi ;
410 }
420 /∗ ∗ E j e m p l i f i c a N e g a t i v e A r r a y S i z e E x c e p t i o n ∗/
430 p r i v a t e s t a t i c i n t [ ] l l e n a A r r e g l o ( i n t tamanho , i n t v a l o r ) {
440 i n t [ ] a r r e g l o = new i n t [ tamanho ] ;
450 f o r ( i n t i = 0 ; i < tamanho ; i ++) {
460 arreglo [ i ] = valor ;
470 }
480 return a r r e g l o ;
490 }
9.1 Tipos de errores 428
500 /∗ ∗ E j e m p l i f i c a N u l l P o i n t e r E x c e p t i o n ∗/
510 private s t a t i c void l l e n a A r r e g l o ( I n t e g e r [ ] a r r e g l o , i n t v a l o r ){
520 f o r ( i n t i = 0 ; i < a r r e g l o . l e n g t h ; i ++) {
530 a r r e g l o [ i ] = new I n t e g e r ( v a l o r ) ;
540 }
550 }
560 /∗ ∗ E j e m p l i f i c a I n p u t M i s m a t c h E x c e p t i o n de j a v a . u t i l ∗/
570 private s t a t i c void leeNumeros ( ) {
580 S c a n n e r s I n = new S c a n n e r ( System . i n ) ;
590 while ( true ) {
600 System . o u t . p r i n t l n ( "Dame un número" ) ;
610 i n t num = s I n . n e x t I n t ( ) ;
620 System . o u t . p r i n t l n ( "El número leı́do es:" + num ) ;
630 }
640 System . o u t . p r i n t l n ( "Salı́ del while " ) ;
650 }
660
670 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
680 S c a n n e r s I n = new S c a n n e r ( a r g s [ 0 ] ) ;
690 int cual = sIn . nextInt ( );
700 I n t e g e r [ ] i n t A r r a y = new I n t e g e r [ 5 ] ;
710 I n t e g e r [ ] malos = n u l l ;
720 switch ( c u a l ) {
730 case 1 : // A r i t h m e t i c
740 System . o u t . p r i n t l n ( " Llamamos a calcula (0):"
750 + calcula (0));
760 break ;
770 case 2 : // A r r a y S t o r e
780 System . o u t . p r i n t l n ( " ArrayStoreException :" ) ;
790 g u a r d a O b j e t o ( i n t A r r a y , 0 , "abc" ) ;
800 break ;
810 case 3 : // C l a s s C a s t E x c e p t i o n
820 System . o u t . p r i n t l n ( " ClassCastExceptionException " ) ;
830 i n t e r p r e t a O b j e t o ( i n t A r r a y , 0 , "abc" ) ;
840 break ;
850 case 4 : // I l l e g a l A r g u m e n t E x c e p t i o n
860 System . o u t . p r i n t l n ( " IllegalArgumentException " ) ;
870 System . o u t . p r i n t l n ( " Cálculo de raı́z cuarta "
880 + raizCuarta ( 5.0));
890 break ;
900 case 5 : // A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n
910 int [ ] enteros = {6 ,2 ,6 ,8};
920 System . o u t . p r i n t l n ( " Llamamos a maximo (enteros , 4)="
930 + maximo ( e n t e r o s , 4 ) ) ;
940 break ;
429 Manejo de errores en ejecución
950 case 6 : // N e g a t i v e A r r a y S i z E x c e p t i o n
960 i n t [ ] b u e n o s = l l e n a A r r e g l o ( 5 , 1);
970 System . o u t . p r i n t l n ( b u e n o s . l e n g t h ) ;
980 break ;
990 case 7 : // N u l l P o i n t e r E x c e p t i o n
1000 System . o u t . p r i n t l n ( " Llenando arreglo nulo" ) ;
1010 l l e n a A r r e g l o ( malos , 7 ) ;
1020 break ;
1030 case 8 : // I n p u t M i s m a t c h E x c e p t i o n
1040 System . o u t . p r i n t l n ( " Leyendo enteros " ) ;
1050 leeNumeros ( ) ;
1060 break ;
1070 }
1080 } // main
1090 }
4
Java contempla el valor infinity, por lo que una división donde el dividendo sea un número
real y el divisor 0 no lanza esta excepción, sino que entrega infinity como resultado.
9.1 Tipos de errores 430
puede hacer la coerción, por lo que lanza una excepción. La ejecución del
programa ClassCE la podemos ver en la figura 9.3.
La clase Exception es una clase muy sencilla que tiene realmente muy po-
cos métodos. De hecho, únicamente tiene dos constructores que se pueden usar
en aquellas clases que hereden a Exception. La clase Throwable, superclase de
Exception, es la que cuenta con algunos métodos más que se pueden invocar desde
cualquier excepción, ya sea ésta de Java o del programador. Veamos primero los
dos constructores de Exception.
public Exception() Es el constructor por omisión. La única información que pro-
porciona es el nombre de la excepción.
9.3 Cómo detectar y atrapar una excepción 434
Constructores:
public Throwable() Es el constructor por omisión.
public Throwable(String msg) Da la oportunidad de construir el objeto con infor-
mación que se transmite en la cadena msg.
Método descriptivo:
Regresa en una cadena el nombre de la clase a la que per-
public String toString()
tenece y la cadena con la que fue creada, en su caso.
Como la clase Exception extiende a la clase Throwable, cualquier excepción
que nosotros declaremos que extienda a Exception contará con los métodos de
Throwable.
Las excepciones de tiempo de ejecución siempre van a imprimir la cadena
producida por toString() y la pila de ejecución producida por printStackTrace().
De esa manera tenemos información muy puntual de cuál fue el tipo de error y
dónde exactamente se presentó.
435 Manejo de errores en ejecución
Sin embargo, también pueden presentarse excepciones que no son de las lla-
madas “de tiempo de ejecución” (RuntimeException) y que sı́ deben ser vigila-
das y manejadas de alguna manera. Entre ellas podemos mencionar las que son
lanzadas en relación con procesos de entrada y salida (IOException); cuando du-
rante la ejecución de una aplicación no se encuentra una clase que se debe cargar
(ClassNotFoundException); cuando se pretende hacer una copia de un objeto que no
es duplicable (CloneNotSupportedException); cuando se da alguna interrupción en
un proceso que se está ejecutando (InterruptedException); y algunas más. Además,
el usuario puede declarar sus propias clases de excepciones, que pueden extender
a una clase particular de excepciones o a la clase genérica Exception.
5
En este caso subrayamos lo que forma parte del enunciado en Java para distinguirlo de lo
que marca repetición de algún segmento.
9.3 Cómo detectar y atrapar una excepción 436
10 package e x c e p c i o n e s ;
20 import j a v a . i o . ∗ ;
30 import j a v a . u t i l . S c a n n e r ;
40 import j a v a . u t i l . I n p u t M i s m a t c h E x c e p t i o n ;
9.3 Cómo detectar y atrapar una excepción 438
60 p u b l i c c l a s s CatchExc {
70 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
80 int k = 0;
90 int c ;
100 int i = 3;
110 i n t [ ] e n t e r o s = new i n t [ 3 ] ;
120 S c a n n e r s c a n = new S c a n n e r ( System . i n ) ;
130 try {
140 System . o u t . p r i n t l n ( "Dame un entero para trabajar " ) ;
150 w h i l e ( ( c = System . i n . r e a d ( ) ) >= ’0’ && c <= ’9’ )
160 k = k ∗ 10 + ( c ( i n t ) ’0’ ) ;
170 System . o u t . p r i n t l n ( "Lei el valor " + k ) ;
180 c = 1000/ k ;
190 System . o u t . p r i n t l n ( "El valor final de c es " + c ) ;
200 System . o u t . p r i n t l n ( "Dame otro entero :" ) ;
210 c = scan . n e x t I n t ( ) ;
220 i f ( c != 0 )
230 System . o u t . p r i n t l n ( " División :" + ( k / c ) ) ;
240 enteros [ i ] = c ;
250 } catch ( E x c e p t i o n e ) {
260 System . o u t . p r i n t l n ( "La excepción es de la clase : \n\t"
270 + e . getClass ());
280 }
290 System . o u t . p r i n t l n ( "A punto de salir normalmente "
300 + "del metodo main" ) ;
310 }
320 }
teclean caracteres –lı́neas 60 y 70–, por lo que se lanza una excepción que indica
que la entrada no coincide con lo esperado –InputMismatchException–.
En el segundo caso –figura 9.6– se proporciona el valor 0 para la variable k, lo
que provoca una división entre 0 y el lanzamiento de la excepción correspondiente,
ArithmeticException.
Figura 9.5 Excepciones de tiempo de ejecución atrapadas con una superclase (A)
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc
20 Dame un e n t e r o p a r a t r a b a j a r
30 35
40 L e i e l v a l o r 35
50 E l v a l o r f i n a l de c e s 28
60 Dame o t r o e n t e r o :
70 lmn
80 La e x c e p c i ó n e s de l a c l a s e :
90 class java . u t i l . InputMismatchException
100 A punto de s a l i r normalmente d e l metodo main
110 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
Figura 9.6 Excepciones de tiempo de ejecución atrapadas con una superclase (B)
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc
20 Dame un e n t e r o p a r a t r a b a j a r
30
40 Lei el valor 0
50 La e x c e p c i ó n e s de l a c l a s e :
60 class java . lang . ArithmeticException
70 A punto de s a l i r normalmente d e l metodo main
80 e l i s a @ l a m b d a : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
lı́nea 310, por lo que la lı́nea 330 del programa, que se encuentra a continuación del
bloque de la excepción, ya no se ejecuta. En este ejemplo usamos varios métodos
de la clase Class, como getClass(), que regresa el “tipo” de la clase, y newIns-
tance(), que construye un objeto de la clase especificada. La clase Class contiene
muchos métodos que se usan para obtener las descripciones de cualquier clase que
se encuentre disponible.
Figura 9.8 Ejecución con relanzamiento de la excepción
10 . . . p r o g r a m a s $ j a v a e x c e p c i o n e s / CatchExc1
20 Dame un e n t e r o p a r a t r a b a j a r
30 0
40 Lei el valor 0
50 La e x c e p c i ó n e s de l a c l a s e :
60 class java . lang . ArithmeticException
70 E x c e p t i o n i n t h r e a d "main" j a v a . l a n g . A r i t h m e t i c E x c e p t i o n
80 a t sun . r e f l e c t . N a t i v e C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e 0 ( N a t i v e
90 Method )
100 a t sun . r e f l e c t . N a t i v e C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e ( N a t i v e C
110 onstructorAccessorImpl . java :57)
120 a t sun . r e f l e c t . D e l e g a t i n g C o n s t r u c t o r A c c e s s o r I m p l . n e w I n s t a n c e ( Del
130 egatingConstructorAccessorImpl . java :45)
140 at java . lang . r e f l e c t . Constructor . newInstance ( Constructor . java :532)
150 at java . lang . Class . newInstance0 ( Class . java :372)
160 at java . lang . Class . newInstance ( Class . java :325)
170 a t e x c e p c i o n e s . CatchExc1 . main ( CatchExc1 . j a v a : 3 1 )
180 e l i s a @ e l i s a d e s k t o p : ˜ / ICC1 / n o t a s / n o t a s 2 0 1 0 / p r o g r a m a s $
10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s D i v P o r C e r o E x c e p t i o n extends A r i t h m e t i c E x c e p t i o n {
30 public DivPorCeroException () {
40 super ( ) ;
50 }
60 p u b l i c D i v P o r C e r o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }
9.3 Cómo detectar y atrapar una excepción 442
10 package e x c e p c i o n e s ;
20 import j a v a . u t i l . S c a n n e r ;
30 p u b l i c c l a s s D i v P o r Ce r o U s o {
40 public static float sqrt ( float x )
50 throws D i v P o r C e r o E x c e p t i o n {
60 i f ( x < 0)
70 throw new D i v P o r C e r o E x c e p t i o n ( "Se pide raiz de numero "
80 + " negativo !" ) ;
90 else
100 r e t u r n ( ( f l o a t ) Math . s q r t ( x ) ) ;
110 }
120
130 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
140 // Aca l e e r i a m o s v a l o r p a r a r en v e z de c a l c u l a r l o a l a z a r
150 f l o a t r = ( ( f l o a t ) Math . random ( ) ∗ 1 0 0 0 0 ) ;
160 f l o a t o t r o = ( f l o a t ) ( Math . random ( ) + . 5 ) ;
170 System . o u t . p r i n t l n ( "Otro: "+ o t r o ) ;
180 int signo = ( otro > 1 ? 0 : 1 );
190 i f ( s i g n o == 0 )
200 r = r ;
210 // Aca t e r m i n a m o s de o b t e n e r un v a l o r p a r a r
220 try {
230 System . o u t . p r i n t l n ( "La raiz cuadrada de "+ r + " es "
240 + sqrt ( r ));
250 }
260 catch ( D i v P o r C e r o E x c e p t i o n e ) {
270 System . o u t . p r i n t l n ( e . g e t M e s s a g e ( ) ) ;
280 System . e x i t ( 1);
290 }
300 }
310 }
10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s D i v P o r C e r o 2 E x c e p t i o n extends E x c e p t i o n {
30 public DivPorCero2Exception () {
40 super ( ) ;
50 }
60 p u b l i c D i v P o r C e r o 2 E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }
Una vez corregido esto, la llamada al método sqrt tiene que aparecer dentro
de un bloque try que se encargue de detectar la excepción que lanza el método.
Como la excepción se maneja totalmente dentro del método en cuestión, que
en este caso es main, la excepción no se propaga hacia afuera de main, por lo que
el método no tiene que avisar que pudiera lanzar una excepción.
Como ya mencionamos antes, las excepciones se pueden lanzar en cualquier
momento: son simplemente un enunciado más. Por supuesto que un uso racional
de ellas nos indica que las deberemos asociar a situaciones no comunes o crı́ticas,
pero esto último tiene que ver con la semántica de las excepciones, no con la
sintaxis.
Tal vez el ejemplo del listado 9.5 en la página opuesta no muestre lo útil que
pueden ser las excepciones, porque redefinen de alguna manera una excepción
que la JVM lanzarı́a de todos modos. Pero supongamos que estamos tratando de
armar una agenda telefónica, donde cada individuo puede aparecer únicamente
una vez, aunque tenga más de un teléfono. Nuestros métodos de entrada, al tratar
de meter un nombre, detectan que ese nombre con la dirección ya está registrado.
En términos generales, esto no constituye un error para la JVM, pero si para el
9.3 Cómo detectar y atrapar una excepción 444
10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s R e g D u p l i c a d o E x c e p t i o n extends E x c e p t i o n {
30 public RegDuplicadoException () {
40 super ( ) ;
50 }
60 p u b l i c R e g D u p l i c a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }
10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s R e g N o E n c o n t r a d o E x c e p t i o n extends E x c e p t i o n {
30 public RegNoEncontradoException ( ) {
40 super ( ) ;
50 }
60 p u b l i c R e g N o E n c o n t r a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 }
10 package e x c e p c i o n e s ;
20 p u b l i c c l a s s BaseDeDatos {
30 i n t numRegs ;
40 ...
50 public void agrega ( R e g i s t r o reg )
60 throws R e g D u p l i c a d o E x c e p t i o n {
70 ...
80 i f ( actual . equals ( reg ))
90 throw new R e g D u p l i c a d o E x c e p t i o n ( r e g . nombre ) ;
100 ...
110 }
445 Manejo de errores en ejecución
120 ...
130 public void e l i m i n a ( R e g i s t r o reg )
140 throws R e g N o E n c o n t r a d o E x c e p t i o n {
150 ...
160 i f ( a c t u a l == n u l l )
170 throw new R e g N o E n c o n t r a d o E x c e p t i o n ( r e g . nombre ) ;
180 ...
190 }
200 ...
210 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
220 ...
230 w h i l e ( o p c i o n != 0 ) {
240 try {
250 switch ( opcion ) {
260 case 1 : a g r e g a ( r e g ) ;
270 reportaAgregado ( ) ;
280 break ;
290 case 2 : e l i m i n a ( r e g ) ;
300 reportaEliminado ();
310 break ;
320 ...
330 ...
340 } // s w i t c h
350 } // t r y
360 catch ( R e g D u p l i c a d o E x c e p t i o n e ) {
370 // p r o d u c e un manejo a d e c u a d o de l a r e p e t i c i ó n
380 System . o u t . p r i n t l n ( "El registro de: "
390 + e . getMessage ( )
400 + "ya existe , por lo que no se agregó " ) ;
410 } // R e g D u p l i c a d o E x c e p t i o n
420 catch ( R e g N o E n c o n t r a d o E x c e p t i o n e ) {
430 // P ro d uc e un manejo ad e c u ad o a l no e n c o n t r a r a l
440 // nombre
450 System . o u t . p r i n t l n ( "El registro de: "
460 + e . getMessage ( )
470 + "no se encontró , por lo que no se eliminó " ) ;
480 } // R e g N o E n c o n t r a d o E x c e p t i o n
490 } // w h i l e
500 ...
510 } // main
520 } // c l a s s
El listado 9.9 en la página opuesta hace uso del hecho de que cuando en un
bloque se presenta una excepción, la ejecución salta a buscar el manejador de la
excepción y deja de ejecutar todo lo que esté entre el punto donde se lanzó la
9.4 Las clases que extienden a Exception 446
pos, uno para cada registro, y dos métodos, el que localiza al elemento inmediato
anterior en la lista y el que localiza al inmediato posterior. En los listados 9.10
y 9.11 podemos ver un bosquejo de cómo se lograrı́a esto.
10 p u b l i c c l a s s R e g N o E n c o n t r a d o E x c e p t i o n extends E x c e p t i o n {
20 p r i v a t e R e g i s t r o regAnt , r e g P o s t ;
30 public RegNoEncontradoException ( ) {
40 super ( ) ;
50 }
60 p u b l i c R e g N o E n c o n t r a d o E x c e p t i o n ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 public RegNoEncontradoException ( R e g i s t r o a n t e r i o r ,
100 Registro actual ) {
110 regAnt = b u s c a A n t e r i o r ( padre ) ;
120 regPost = buscaPosterior ( actual ) ;
130 }
140 private Registro buscaAnterior ( Registro a n t e r i o r ) {
150 ...
160 }
170 private Registro buscaPosterior ( Actual ) {
180 ...
190 }
200 p u b l i c R e g i s t r o daRegAnt ( ) {
210 return regAnt ;
220 }
230 p u b l i c R e g i s t r o daRegPost ( ) {
240 return regPost ;
250 }
260 }
10 p u b l i c c l a s s E j e m p l o {
20 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
30 ...
40 try {
50 i f ( a c t u a l . s i g == n u l l ) {
60 throw new
70 RegNoEncontradoException ( a c t u a l . g e t A n t e r i o r ( ) ,
80 actual );
90 } // f i n de i f
100 ...
110 } // f i n de t r y
120 catch ( R e g N o E n c o n t r a d o E x c e p t i o n e ) {
9.4 Las clases que extienden a Exception 448
10 c l a s s M i E x c e p c i o n 2 extends E x c e p t i o n {
20 private int i ;
30 public MiExcepcion2 () {
40 super ( ) ;
50 }
60 p u b l i c M i E x c e p c i o n 2 ( S t r i n g msg ) {
70 super ( msg ) ;
80 }
90 p u b l i c M i E x c e p c i o n 2 ( S t r i n g msg , i n t x ) {
100 super ( msg ) ;
110 i = x;
120 }
130 public int val () {
140 return i ;
150 }
160 }
10 p u b l i c c l a s s C a r a c t e r i s t i c a s E x t r a {
20 p u b l i c s t a t i c v o i d f ( ) throws M i E x c e p c i o n 2 {
30 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde f()" ) ;
40 throw new M i E x c e p c i o n 2 ( ) ;
50 }
60 p u b l i c s t a t i c v o i d g ( ) throws M i E x c e p c i o n 2 {
70 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde g()" ) ;
80 throw new M i E x c e p c i o n 2 ( "Se originó en g()" ) ;
90 }
100 p u b l i c s t a t i c v o i d h ( ) throws M i E x c e p c i o n 2 {
110 System . o u t . p r i n t l n ( " Lanzando MiExcepcion2 desde h()" ) ;
120 throw new M i E x c e p c i o n 2 ( "Se originó en h()" , 4 7 ) ;
130 }
140 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
150 try {
160 f ();
170 }
180 catch ( M i E x c e p c i o n 2 e ) {
190 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
200 }
210 try {
220 g();
230 }
240 catch ( M i E x c e p c i o n 2 e ) {
250 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
260 }
9.4 Las clases que extienden a Exception 450
270 try {
280 h();
290 }
300 catch ( M i E x c e p c i o n 2 e ) {
310 e . p r i n t S t a c k T r a c e ( System . e r r ) ;
320 System . e r r . p r i n t l n ( "e.val () = " + e . v a l ( ) ) ;
330 }
340 }
350 }
10 c l a s s T r e s E x c e p t i o n extends E x c e p t i o n {
20 public TresException () {
30 super ( ) ;
40 }
50 p u b l i c T r e s E x c e p t i o n ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }
90 p u b l i c c l a s s F i n a l l y T r a b a j a {
100 s t a t i c int cuenta = 0;
110 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
120 while ( true ) {
130 try {
140 // Post i n c r e m e n t o . Es c e r o l a p r i m e r a v e z
150 i f ( c u e n t a++ == 0 ) {
160 throw new T r e s E x c e p t i o n ( ) ;
170 }
180 System . o u t . p r i n t l n ( "No hubo excepción " ) ;
190 }
200 catch ( T r e s E x c e p t i o n e ) {
210 System . e r r . p r i n t l n ( " TresException " ) ;
220 }
230 finally {
240 System . e r r . p r i n t l n ( "En la cláusula finally " ) ;
250 i f ( c u e n t a == 2 ) {
260 break ; // s a l d e l w h i l e
270 }
280 }
290 }
300 }
310 }
10 c l a s s S w i t c h {
20 boolean s t a t e = f a l s e ;
30 boolean r e a d ( ) {
40 return s t a t e ;
50 }
60 v o i d on ( ) {
70 s t a t e = true ;
80 }
90 void o f f ( ) {
100 state = false ;
110 }
120 }
10 c l a s s O n O f f E x c e p t i o n 1 extends E x c e p t i o n {
20 public OnOffException1 () {
30 super ( ) ;
40 }
50 p u b l i c O n O f f E x c e p t i o n 1 ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }
9.5 El enunciado finally 454
10 c l a s s O n O f f E x c e p t i o n 2 extends E x c e p t i o n {
20 public OnOffException2 () {
30 super ( ) ;
40 }
50 p u b l i c O n O f f E x c e p t i o n 2 ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }
10 c l a s s O n O f f S w i t c h {
20 s t a t i c S w i t c h sw = new S w i t c h ( ) ;
30 s t a t i c void f ( )
40 throws O n O f f E x c e p t i o n 1 , O n O f f E x c e p t i o n 2 {
50 }
60 }
10 p u b l i c c l a s s C o n F i n a l l y {
20 s t a t i c S w i t c h sw = new S w i t c h ( ) ;
30 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
40 try {
50 sw . on ( ) ;
60 // C ó d i g o que puede l a n z a r e x c e p c i o n e s
70 OnOffSwitch . f ( ) ;
80 }
90 catch ( O n O f f E x c e p t i o n 1 e ) {
100 System . e r r . p r i n t l n ( " OnOffException1 " ) ;
110 }
120 catch ( O n O f f E x c e p t i o n 2 e ) {
130 System . e r r . p r i n t l n ( " OnOffException2 " ) ;
140 }
150 finally {
160 sw . o f f ( ) ;
170 }
180 }
190 }
10 c l a s s C u a t r o E x c e p t i o n extends E x c e p t i o n {
20 public CuatroException () {
30 super ( ) ;
40 }
50 p u b l i c C u a t r o E x c e p t i o n ( S t r i n g msg ) {
60 super ( msg ) ;
70 }
80 }
10 p u b l i c c l a s s S i e m p r e F i n a l l y {
20 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
30 System . o u t . p r i n t l n ( " Entrando al primer bloque try" ) ;
40 try {
50 System . o u t . p r i n t l n ( " Entrando al segundo bloque try" ) ;
60 try {
70 throw new C u a t r o E x c e p t i o n ( ) ;
80 }
90 finally {
100 System . o u t . p r i n t l n ( " Finally en el segundo "
110 + " bloque try" ) ;
120 }
130 }
140 catch ( C u a t r o E x c e p t i o n e ) {
150 System . e r r . p r i n t l n ( " Atrapando CuatroException en " +
160 "el primer bloque try" ) ;
170 }
180 finally {
190 System . e r r . p r i n t l n ( " Finally en primer bloque try" ) ;
200 }
210 }
220 }
Con esto damos por terminado este tema, aunque lo usaremos extensivamente
en los capı́tulos que siguen.
Ejercicios
9.1.- ¿Qué está mal en el código siguiente? Identifica todos los errores de sintaxis.
10 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
20 S c a n n e r e s c a n e r = new S c a n n e r ( System . i n ) ;
30 try {
40 i n t num = e s c a n e r . n e x t I n t ( ) ;
50 i f (num > 1 0 0 ) {
60 catch new E x c e p t i o n ( " Fuera del lı́mite " ) ;
70 }
80 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Entrada inválida" ) ;
100 } f i n a l l y ( Exception e ) {
110 System . o u t . p r i n t l n ( " Hecho " ) ;
120 }
130 }
9. Ejercicios 458
9.2.- Determina la salida del pedazo de código siguiente cuando se teclean, suce-
sivamente, las siguientes entradas: (a) 12 (b) -12 (c) a12
10 try {
20 i n t num = e s c a n e r . n e x t I n t ( ) ;
30 i f (num < 0 ) {
40 throw new E x c e p t i o n ( " Negativo no" ) ;
50 }
60 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
70 System . o u t . p r i n t l n ( " Entrada inválida " ) ;
80 } catch ( E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Error : " + e . g e t M e s s a g e ( ) ) ;
100 } finally {
110 System . o u t . p r i n t l n ( " Terminado " ) ;
120 }
9.3.- Determina la salida del pedazo de código anterior si se ponen las lı́neas 80
y 90 antes que las lı́neas 60 y 70, como se muestra a continuación y si se le
alimenta, sucesivamente, las siguientes entradas: (a) 12 (b) -12 (c) a12
10 try {
20 i n t num = e s c a n e r . n e x t I n t ( ) ;
30 i f (num < 0 ) {
40 throw new E x c e p t i o n ( " Negativo no" ) ;
50 }
60 } catch ( E x c e p t i o n e ) {
70 System . o u t . p r i n t l n ( " Error : " + e . g e t M e s s a g e ( ) ) ;
80 } catch ( I n p u t M i s m a t c h E x c e p t i o n e ) {
90 System . o u t . p r i n t l n ( " Entrada inválida " ) ;
100 } finally {
110 System . o u t . p r i n t l n ( " Terminado " ) ;
120 }
9.4.- El siguiente segmento de código reporta varios errores de sintaxis. Dı́ cuáles
son, explica por qué se presentan y dı́ cómo arreglarlos sin eliminar las
excepciones.
459 Manejo de errores en ejecución
10 p u b l i c s t a t i c S t r i n g e j e r c i c i o 4 ( O b j e c t l i s t a ) {
20 i f ( l i s t a == n u l l ) {
30 throw new E x c e p t i o n ( " lista vacı́a " ) ;
40 }
50 return l i s t a . t o S t r i n g ( ) ;
60 }
70
80 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
90 O b j e c t nuevo = n u l l ;
100 System . o u t . p r i n t l n ( " nuevo es: " + e j e r c i c i o 4 ( nuevo ) ) ;
110 }
9.5.- Si el código del ejercicio anterior, en la lı́nea 30, en lugar de lanzar la excep-
ción Exception lanza la excepción NullPointerException, el programa compila
y ejecuta bien. Explica las razones.
9.6.- Tenemos el siguiente método recursivo para calcular el factorial de un entero
positivo:
10 p u b l i c long f a c t o r i a l ( i n t n ) {
20 i f ( n == 1 ) r e t u r n 1 ;
30 r e t u r n n ∗ f a c t o r i a l ( n 1);
40 }
9.10.- Cuando declaras tus propias excepciones, ¿deben ser vigiladas o no? Justi-
fica.
Entrada y salida
10
10.1 Conceptos generales
Uno de los problemas que hemos tenido hasta el momento es que las bases
de datos que hemos estado construyendo no tienen persistencia, esto es, una vez
que se descarga la aplicación de la máquina virtual (que termina la aplicación) la
información que generamos no vive más allá –excepto, posiblemente, si la salida
de la aplicación se redirigió a disco–. No tenemos manera de almacenar lo que
construimos en una sesión para que, en la siguiente sesión, empecemos a partir de
donde nos quedamos. Prácticamente en cualquier aplicación que programemos y
usemos vamos a requerir de mecanismos que proporcionen persistencia a nuestra
información.
En los lenguajes de programación, y en particular en Java, esto se logra median-
te archivos, que son conjuntos de datos guardados en un medio de almacenamiento
externo. Los archivos sirven de puente entre la aplicación y el medio exterior, ya
sea para comunicarse con el usuario o para, como acabamos de mencionar, darle
persistencia a nuestras aplicaciones.
Hasta ahora hemos usado extensamente la clase Scanner, que nos permite apli-
car mecanismos de revisión de cadenas a la consola (o a una cadena). También
hemos usado dos archivos (objetos) que están dados en Java y que son System.out
y System.err. Ambos objetos corresponden a archivos de salida; el primero es para
10.1 Conceptos generales 462
que se quisiera extender un archivo que ya existe (append ). En este último caso
también deberá verificar que el archivo ya exista y que esté disponible. Tanto en
flujos de entrada como de salida el sistema deberá verificar que la aplicación tenga
los permisos necesarios para acceder, crear y/o escribir en el flujo especificado.
Las actividades más tardadas que lleva a cabo una aplicación son las de entra-
da y salida, pues tienen que comunicarse con el mundo exterior de los dispositivos
fı́sicos. En general, el sistema se tiene que “sentar” a esperar que fluya la infor-
mación; para acortar estos tiempos el sistema lee (o escribe) por bloques: lee un
bloque completo y va proporcionando de ahı́ dato por dato; cuando el bloque de
memoria se vacı́a, carga un nuevo bloque; o escribe a un bloque de memoria (lla-
mado buffer ) y cuando el bloque se llena es cuando lo escribe al dispositivo fı́sico.
Decimos entonces que el proceso de entrada/salida está “gestionado” a través de
un buffer.
Los flujos de entrada y salida, como su nombre lo indica, fluyen. En un flujo de
entrada la aplicación va consumiendo el flujo como se le va presentando, por lo que
al abrir el flujo deberemos tener un apuntador o referencia a cuál es el siguiente
10.2 Jerarquı́a de clases 464
elemento disponible del flujo, que deberı́a ser el primer elemento del mismo; en
el caso de un flujo de salida deberemos saber hasta dónde se ha emitido el flujo
y que al abrirse deberá decir que no se ha emitido nada. Con cada operación de
entrada/salida esta referencia se debe actualizar para que el objeto sea capaz de
ir entregando (produciendo) los caracteres conforme se solicitan (emiten).
Podemos ver un esquema de este funcionamiento en las figuras 10.3 y 10.4,
donde la aplicación los produce y consume en el orden en que se muestran en el
flujo.
Dispositivo D
A
T Leer
O S Aplicación
Escribir
Aplicación D
A
T
O S Dispositivo
DataInput
InputStream ObjectInput
ObjectInputStream
AudioInputStream
LineNumberInputStream
SequenceInputStream
DataInputStream
ByteArrayInputStream
BufferedInputStream
FilterInputStream
PushBackInputStreeam
FileInputStream
CheckedInputStream
PipedInputStream
CipherInputStream
SequenceInputStream
DigestInputStream
StringBufferInputStream
InflaterInputStream
ProgressMonitorInputStream
1
Corresponde a deprecated, que indica que no se recomienda su uso porque ya no va a ser
actualizada y soportada
469 Entrada y salida
DataOutput
OutputStream ObjectOutput
ObjectOutputStream
ByteArrayOutputStream
PipedOutputStream
FileOutputStream
FilterOutputStream
PrintStream
BufferedOutputStream
DataOutputStream
BufferedWriter
CharArrayWriter
FilterWriter
Writer PrintWriter
PipedWriter
StringWriter
OutputStreamWriter FileWriter
471 Entrada y salida
StringReader
CharArrayReader
PipedReader
Reader
BufferedReader LineNumberReader
FilterReader PushbackReader
InputStreamReader FileReader
Filtro
Origen Destino
También estas jerarquı́as corren paralelas a las que trabajan con bytes, por lo
que no daremos una nueva explicación de cada una de ellas. Se aplica la misma
explicación, excepto que donde dice “byte” hay que sustituir por “carácter”. Úni-
10.4 Entrada y salida de caracteres 472
Vale la pena hacer la aclaración que en este caso los flujos que leen de y escriben
a archivos en disco extienden a las clases InputStreamReader y OutputStreamWriter
respectivamente, ya que la unidad de trabajo en los archivos es el byte (8 bits) y
no el carácter (16 bits). Por lo demás funcionan igual que sus contra partes en los
flujos de bytes.
Es conveniente mencionar que las versiones actuales de Java indican que las
clases que se deben usar son las que derivan de Reader y Writer y no las que son
subclases de InputStream y OutputStream. Ambas jerarquı́as (las de bytes y las
de caracteres) definen prácticamente los mismos métodos para bytes y caracteres,
pero para fomentar la portabilidad de las aplicaciones se ha optado por soportar
de mejor manera las clases relativas a caracteres.
Sin embargo, como ya mencionamos, la entrada y salida estándar de Java es
a través de clases que pertenecen a la jerarquı́a de bytes (System.in, System.out y
System.err).
Lo primero que queremos poder hacer es leer desde el teclado y escribir a
pantalla. Esto lo necesitamos para la clase que maneja el menú y de esta manera
ir abriendo las cajas negras que nos proporcionaba la clase Consola para este fin.
Por ser éstos tres objetos estáticos de la clase System se pueden usar sin cons-
truir objetos. Todo programa en ejecución cuenta con ellos, por lo que los puede
usar, simplemente refiriéndose a ellos a través de la clase System.
El primero de ellos es un archivo al que dirigiremos los mensajes que se refieran
a errores, y que no queramos “mezclar” con la salida normal. El segundo objeto es
para leer de teclado (con eco en la pantalla) y el tercero para escribir en la pantalla.
Las dos clases mencionadas son clases concretas que aparecen en la jerarquı́a de
clases que mostramos en las figuras 10.5 en la página 466 y 10.6 en la página 469.
Si bien la clase PrintStream se va a comportar exactamente igual a Consola,
en cuanto a que “interpreta” enteros, cadenas, flotantes, etc. para mostrarlos con
formato adecuado, esto no sucede con la clase InputStream que opera de manera
muy primitiva, leyendo byte por byte, y dejándole al usuario la tarea de pegar los
bytes para interpretarlos. Más adelante revisaremos con cuidado todos los méto-
dos de esta clase. Por el momento únicamente revisaremos los métodos que leen
byte por byte, y que son:
Como podemos ver de los métodos de la clase InputStream, son muy primitivos
y difı́ciles de usar. Por ello, como primer paso en la inclusión de entrada y salida
completa en nuestra aplicación, para entrada utilizaremos una subclase de Reader,
BufferedReader, más actual y mejor soportada.
Esta es una clase abstracta que deja sin implementar uno de sus métodos. El
constructor y los métodos se listan a continuación:
public void write (byte[] b, int off , int len ) throws IOException
Escribe en el flujo de salida los bytes desde b[off] hasta b[off+len-1]. Si
hay un error en el ı́ndice o si b es null, lanza la excepción correspondiente
(como son ArithmeticException ambas no hay que vigilarlas). Si hay algún
error de I/O se lanza la excepción correspondiente.
public void flush () throws IOException
Evacúa el flujo, obligando a que los bytes que estén todavı́a en el buffer
sean escritos al medio fı́sico.
public void close () throws IOException
Cierra el flujo y libera los recursos del sistema asociados al flujo. Una vez
cerrado el flujo, cualquier otro intento de escribir en él va a causar una
excepción. Lanza una excepción (IOException) si se intenta reabrir. En
realidad no hace nada, sino que se tiene que reprogramar para que haga
lo que tiene que hacer.
1 package u t i l e s ;
2 import j a v a . i o . ∗ ;
3 import j a v a . u t i l . S c a n n e r ;
4 /∗ ∗
5 ∗ C l a s s <code>C a t a l o g o C a r r e r a s </code> c a r g a a memoria e l c a t a l o g o de
6 ∗ carreras .
7 ∗ @ a u t h o r <a h r e f =” m a i l t o : e l i s a @ l a m b d a . f c i e n c i a s . unam . mx”></a>
8 ∗ @version 1.0
9 ∗/
10 public class CatalogoCarreras {
11 /∗ ∗
12 ∗ V a l o r v a r i a b l e <code>c a r r e r a s </code> p a r a l a s c l a v e s de l a s
13 ∗ carreras .
14 ∗/
15 p r i v a t e s t a t i c S t r i n g c a r r e r a s = "" ;
16 /∗ ∗
17 ∗ V a l o r v a r i a b l e <code>s C a r r e r a s </code> p a r a l o s nombres de l a s
18 ∗ carreras .
19 ∗/
20 p r i v a t e s t a t i c S t r i n g s C a r r e r a s = "" ;
21 /∗ ∗
22 ∗ V a l o r v a r i a b l e <code>h a y C a r r e r a s </code> p a r a m a r c a r cuando ya
23 ∗ s e hayan c a r g a d o l a s c a r r e r a s a memoria .
24 ∗/
25 p r i v a t e s t a t i c boolean h a y C a r r e r a s = f a l s e ;
26 /∗ ∗
27 ∗ C o n s t a n t e <code>TAM CLAVE</code> p a r a d a r e l tamaño de l a s
28 ∗ c l a v e s de c a r r e r a .
29 ∗/
30 p u b l i c s t a t i c f i n a l i n t TAM CLAVE = 3 ;
31 /∗ ∗
32 ∗ C o n s t a n t e <code>TAM NOMBRE</code> p a r a d a r e l tamaño de l o s
33 ∗ nombres de c a r r e r a .
34 ∗/
35 p u b l i c s t a t i c f i n a l i n t TAM NOMBRE = 3 6 ;
479 Entrada y salida
37 /∗ ∗
38 ∗ Metodo <code>l e e C a r r e r a s </code> l e e l a s c l a v e s y nombres de l a s
39 ∗ c a r r e r a s d e s d e un a r c h i v o en d i s c o .
40 ∗ @param a r c h i v o v a l o r de t i p o <code>S t r i n g </code> p a r a
41 ∗/
42 public s t a t i c void l e e C a r r e r a s ( S t r i n g a r c h i v o ){
43 Scanner cons = n u l l ;
44 try {
45 c o n s = new S c a n n e r ( new F i l e ( a r c h i v o ) ) ;
46 } catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
47 hayCarreras = false ;
48 System . e r r . p r i n t l n ( "No pude leer el catalogo de carreras " ) ;
49 System . e x i t ( 1 ) ;
50 }
51 hayCarreras = true ;
52 // Se pudo a b r i r e l a r c h i v o
53 i n t n u m Ca r r e r a = 0 ;
54 S t r i n g nombreCarrera = n u l l ;
55 w h i l e ( n u m Ca r r e r a != 1) {
56 n u m Ca r r e r a = c o n s . n e x t I n t ( ) ;
57 i f ( n u m Ca r r e r a != 1) {
58 nombreCarrera = cons . nextLine ( ) ;
59 c a r r e r a s += Cadenas
60 . rellenaCampo ( I n t e g e r . t o S t r i n g ( numCarrera ) ,
61 TAM CLAVE , ’0’ , ’i’ ) ;
62 s C a r r e r a s += Cadenas
63 . r e l l e n a C a m p o ( n o m b r e C a r r e r a ,TAM NOMBRE, ’ ’ , ’d’ ) ;
64 }
65 }
66 cons . c l o s e ( ) ;
67 }
68
69 /∗ ∗
70 ∗ Metodo <code>g e t C a r r e r a s </code> r e g r e s a una c a d e n a con l a s
71 ∗ claves .
72 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
73 ∗/
74 public static String getCarreras () {
75 return c a r r e r a s ;
76 }
77
78 /∗ ∗
79 ∗ Metodo <code>g e t S C a r r e r a s </code> r e g r e s a una c a d e n a con l o s
80 ∗ nombres .
81 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
82 ∗/
83 public static String getSCarreras () {
84 return s C a r r e r a s ;
85 }
10.6 El catálogo de carreras 480
87 /∗ ∗
88 ∗ Metodo <code>b H a y C a r r e r a s </code> r e g r e s a s i ya e s t á n c a r g a d a s
89 ∗ las carreras .
90 ∗ @ r e t u r n v a l o r de t i p o <code>b o o l e a n </code >.
91 ∗/
92 p u b l i c s t a t i c boolean b H a y C a r r e r a s ( ) {
93 return hayCarreras ;
94 }
95
96 /∗ ∗
97 ∗ Metodo <code>d a C a r r e r a </code >, dada l a c l a v e de l a c a r r e r a
98 ∗ r e g r e s a e l nombre .
99 ∗ @param c u a l v a l o r de t i p o <code>i n t </code >.
100 ∗ @ r e t u r n v a l o r de t i p o <code>S t r i n g </code >.
101 ∗/
102 public s t a t i c S t r i n g daCarrera ( i n t c u a l ){
103 i f (! hayCarreras ) {
104 l e e C a r r e r a s ( " CarrerasCiencias .txt" ) ;
105 hayCarreras = true ;
106 }
107 S t r i n g s C u a l = Cadenas . r e l l e n a C a m p o ( I n t e g e r . t o S t r i n g ( c u a l ) ,
108 TAM CLAVE , ’0’ , ’i’ ) ;
109 i n t donde = c a r r e r a s . i n d e x O f ( s C u a l ) ;
110 i f ( donde == 1 | | ( donde % TAM CLAVE) != 0 ) {
111 r e t u r n Cadenas
112 . r e l l e n a C a m p o ( " Código invalido " ,TAM NOMBRE, ’ ’ , ’d’ ) ;
113 } else {
114 i n t empza = ( donde /TAM CLAVE) ∗TAM NOMBRE;
115 r e t u r n s C a r r e r a s . s u b s t r i n g ( empza , empza+TAM NOMBRE ) ;
116 }
117 }
118
119 /∗ ∗
120 ∗ Metodo <code>m u e s t r a C a t a l o g o </code> m u e s t r a e l c a t á l o g o de l a s
121 ∗ carreras .
122 ∗/
123 public s t a t i c void muestraCatalogo ( ) {
124 i f (! hayCarreras ) {
125 l e e C a r r e r a s ( " CarrerasCiencias .txt" ) ;
126 hayCarreras = true ;
127 }
128
129 System . o u t . p r i n t l n ( " Catalogo de carreras de la Facultad "
130 + " de Ciencias \n"
131 +" ================================== "
132 + " ============= " ) ;
481 Entrada y salida
Scanner(Readable source)
construye un Scanner que toma su entrada de un objeto de una clase que
implemente a la interfaz Readable.
Scanner(ReadableByteChannel source)
Construye un nuevo Scanner que produce valores obtenidos del canal es-
pecificado.
Scanner(ReadableByteChannel source, String charsetName)
Construye un nuevo Scanner que produce valores obtenidos del canal es-
pecificado. Los valores están codificados en el conjunto de caracteres es-
pecificado.
Scanner(String source)
Construye un nuevo Scanner que produce valores obtenidos de interpretar
la cadena especificada.
de bytes. Pero lo podemos usar como dispositivo en el que montemos algún otro
tipo de procesador de bytes o caracteres –como Scanner o BufferedReader– para
poder hacer lecturas de más alto nivel.
En cambio, tanto System.out como System.err, que son objetos de la clase
PrintStream interpretan representaciones internas a cadenas, usando los métodos
toString correspondientes. Son muy cómodos, sobre todo si se trata de interaccio-
nar con un usuario.
Hasta ahora únicamente hemos trabajado con la consola o bien con redirec-
cionamiento de la consola, pero no hemos entrado a la motivación principal de
este capı́tulo y que consiste en lograr guardar el estado de nuestra base de datos
para que pueda ser utilizado posteriormente como punto de partida en la siguiente
ejecución.
Podemos almacenar, en primera instancia, la base de datos como un conjunto
de cadenas, y para ello podemos volver a utilizar a los flujos BufferedReader y
BufferedWriter que son muy útiles para leer y escribir cadenas, pero en esta ocasión
queremos que el flujo subyacente sea un archivo en disco y no un flujo estándar.
Revisemos entonces la clase FileReader y FileWriter que me van a dar esa facilidad.
Estos flujos extienden a InputStreamReader y OutputStreamWriter respectivamente,
que a su vez heredan, respectivamente, de Reader y Writer. De esta jerarquı́a
únicamente hemos revisado la clase Reader, ası́ que procedemos a ver las otras
clases de la jerarquı́a que vamos a requerir.
Métodos:
public Writer append(char c) throws IOException
Agrega el argumento al flujo this.
public Writer append(CharSequence csq) throws IOException
CharSequence es una interfaz de Java que básicamente proporciona méto-
dos para convertir una sucesión de caracteres, codificados en cualquier
código de 16 bits en cadenas o subsucesiones. Agrega el argumento al
flujo this.
public Writer append(CharSequence csq, int start , int end)
throws IOException
Agrega al flujo this la subsucesión de csq que empieza en start y termina
en el carácter inmediatamente a la izquierda de end.
public abstract void close () throws IOException
Cierra el flujo vaciándolo primero. Una vez cerrado, cualquier intento de
vaciarlo o escribir en él provocará una excepción de entrada/salida. No
importa que un flujo se intente cerrar una vez cerrado.
public abstract void flush () throws IOException
Provoca que todas las escrituras pendientes sean vaciadas al flujo pro-
porcionado por el sistema operativo. Sin embargo, el sistema operativo
podrı́a no vaciar su buffer.
public void write (int c) throws IOException
Escribe un único carácter en el flujo, tomando los 16 bits bajos del entero
proporcionado. Este método deberı́a ser sobreescrito por las subclases
correspondientes.
public void write (char[] cbuf) throws IOException
Escribe en el flujo los caracteres presentes en el arreglo cbuf.
public abstract void write(char [], int off , int len )
throws IOException
Escribe en el flujo el contenido del arreglo cbuf a partir del carácter en la
posición off y un total de len caracteres.
public void write ( String str ) throws IOException
Escribe el contenido de la cadena en el flujo.
public void write ( String str , int off , int len )
throws IOException
Escribe la subcadena de str desde la posición off un total de len caracteres.
10.8 Persistencia de la base de datos 486
Realmente el único método con el que hay que tener cuidado es el que escribe
un carácter (entero), porque el resto de los métodos se construyen simplemente
invocando a éste.
Las clases que heredan directamente de Reader y Writer son, respectivamente,
InputStreamReader y OutputStreamWriter que pasamos a revisar. Primero revisa-
remos la clase InputStreamReader.
Ahora sı́ ya podemos pasar a revisar las clases FileReader y FileWriter que here-
dan respectivamente de InputStreamReader y OutputStreamWriter. Empezaremos
10.8 Persistencia de la base de datos 488
Para los flujos de salida que escriben a disco tenemos una situación similar a
la de archivos de entrada, pues lo único que se define para la subclase FileWriter
son los constructores. Para el resto de los métodos y campos se heredan las im-
plementaciones dadas por OutputStreamWriter. Veamos la definición.
Constructores:
FileWriter ( File file ) throws IOException
Construye un flujo a disco sobre el flujo file, que podrı́a ser, a su vez, un
FileWriter o cualquier OutputStream o subclases de ésta. La excepción la
lanza si file es un directorio y no un archivo, no existe pero no puede ser
creado o no puede ser abierto por cualquier otra razón.
FileWriter ( File file , boolean append) throws IOException
Construye un flujo a disco sobre el flujo file, que podrı́a ser, a su vez,
un FileWriter o cualquier OutputStream o subclases de ésta. La excepción
la lanza si file es un directorio y no un archivo, no existe pero no puede
ser creado o no puede ser abierto por cualquier otra razón. Si append es
verdadero, la escritura se realiza al final del archivo; si es falsa se realiza
al principio del archivo, como en el constructor sin parámetro booleano.
FileWriter ( FileDescriptor fd)
Construye un flujo a disco asociado con el FileDescriptor.
FileWriter ( String fileName) throws IOException
Construye el flujo dado un nombre. Lanza una excepción de entrada/salida
por las mismas causas que los otros constructores.
FileWriter ( String fileName, boolean append)
throws IOException
Construye el flujo dado un nombre. Si append es verdadera entonces escri-
be al final del archivo; si el archivo no existe lo crea. Lanza una excepción
de entrada/salida por las mismas causas que los otros constructores.
Hay que recordar que la herencia permite que dondequiera que aparezca una
clase como parámetro, los argumentos pueden ser objetos de cualquiera de sus sub-
clases. Con esto en mente pasamos a implementar las opciones en el menú de leer
de un archivo en disco o escribir a un archivo en disco para guardar la información
generada en una sesión dada.
s t a t i c f i n a l i n t AGREGA = 1 ,
...
LEER = 6 ,
GUARDAR = 7 ,
PEGARDISCO = 8 ;
29 /∗ ∗
30 ∗ P i d e a l u s u a r i o e l nombre d e l a r c h i v o en e l que d e s e a
31 ∗ e s c r i b i r o d e l que d e s e a l e e r .
32 ∗ @param c o n s D i s p o s i t i v o d e l que va a l e e r e l nombre
33 ∗ @param l e c t u r a True : l e c t u r a , f a l s e : e s c r i t u r a .
34 ∗ @return e l archivo s o l i c i t a d o .
35 ∗/
36 p u b l i c S t r i n g pideNombreArch ( B u f f e r e d R e a d e r cons , i n t c a s o )
37 throws I O E x c e p t i o n {
38 S t r i n g m e n s a j e = "Por favor dame el nombre del archivo \n"
39 + ( c a s o == LEER? "del que vas a leer registros "
40 : ( c a s o == GUARDAR
41 ? "en el que vas a guardar la base de datos "
42 : "en el que vas a agregar registros " ) )
43 + ":\t" ;
44 S t r i n g nombre ;
45 try {
46 System . o u t . p r i n t ( m e n s a j e ) ;
47 nombre = c o n s . r e a d L i n e ( ) ;
48 } catch ( I O E x c e p t i o n e ) {
49 throw e ;
50 } // end o f t r y c a t c h
51 r e t u r n nombre ;
52 }
En las lı́neas 307 y 308 solicitamos el nombre del archivo a usar y procedemos
a abrir el archivo. En este punto la única excepción que pudo haber sido lanzada
es en la interacción con el usuario, ya que la apertura de un archivo en disco
difı́cilmente va a lanzar una excepción.
El algoritmo para leer registros de una archivo en disco es la imagen del proceso
para guardar. Éste se puede ver en la figura 10.11.
Para identificar el archivo del que vamos a leer usamos el mismo método,
excepto que con un mensaje apropiado. Al abrir el archivo automáticamente nos
encontraremos frente al primer registro. A partir de ahı́, suponemos que el archivo
está correcto y que hay cuatro cadenas sucesivas para cada registro que vamos a
leer. El código que corresponde a esta opción se encuentra en el listado 10.4 en la
siguiente página.
10.8 Persistencia de la base de datos 494
$ $
'
' '
' Identificar archivo
'
' '
&
'
' Abrir el archivo
'
' Inicio
'
' '
'
' '
'
Colocarse al prin-
'
' %
'
' cipio del archivo
Leer registros &
a la Base de Datos # #
'
desde un archivo en disco '
'
' Procesar registro Leer registro de disco
'
' P roceso
'
' (mientras haya) Pasar al siguiente
'
'
'
'
'
'
'
' !
%F inal Cerrar el archivo
Código 10.4 Opción para leer registros desde disco MenuListaIO (1/2)
Código 10.4 Opción para leer registros desde disco MenuListaIO (2/2)
234 i f ( c u e n t a == n u l l ) {
235 c u e n t a = " 000000000 " ;
236 System . o u t . p r i n t l n ( "No hubo cuenta " ) ;
237 } // end o f i f ( c u e n t a == n u l l )
238 i f ( c l a v e == n u l l ) {
239 c l a v e = "????" ;
240 System . o u t . p r i n t l n ( "No hubo clave " ) ;
241 } // end o f i f ( c l a v e == n u l l )
242 } // end o f c a t c h
243 finally {
244 i f ( a r c h i v o I n != n u l l ) {
245 try {
246 archivoIn . close ();
247 } catch ( I O E x c e p t i o n e ) {
248 System . e r r . p r i n t l n ( "No pude cerrar el"
249 + " archivo de lectura " ) ;
250 } // end o f t r y c a t c h
251 } // end o f i f ( a r c h i v o I n != n u l l )
252 } // end o f f i n a l l y
253 r e t u r n LEER ;
En las lı́neas 303 y 304, donde se usa otro constructor para el flujo, el que
permite indicar si se usa un archivo ya existente para agregar a él.
En las lı́neas 307 a 313 se usa el método append en lugar del método write,
ya que deseamos seguir agregando al final del archivo.
0 0 0 4 0 0 2 8 0 0 0 9 0 0 0 4 0 0 1 4 4 3 7 2 6 9 7A . .... .
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 . . . . . . 49
Lo que conviene es que la clase para cada registro nos entregue el tamaño de
cada campo. Podemos suponer que esto es ası́, agregando a la clase InfoEstudiante
un arreglo con esta información:
s h o r t [ ] tamanhos = { 4 , 4 0 , 9 , 4 , 2 0 } ;
quedando en tamanhos[0] el número de campos, en tamanhos[1] el tamaño del primer
campo y ası́ sucesivamente. Agregamos a la clase un método
p u b l i c s h o r t getTamanho ( i n t campo ) {
r e t u r n tamanhos [ campo ] ;
}
que simplemente regresa el tamaño del campo solicitado.
Podemos pensar en un archivo que es heterogéneo, en el sentido de que lo que
llamamos el encabezado del mismo no tiene la forma que el resto de los elementos;
éstos se componen de n campos – la n viene en los primeros dos bytes del archivo
con formato binario de un entero corto (short) – con un total de k bytes que
corresponde a la suma de los n enteros cortos que aparecen a partir del byte 2 del
archivo. El encabezado del archivo consiste de 2pn 1q bytes. Una vez procesados
estos n 1 enteros cortos, el resto del archivo lo podemos ver como un arreglo
unidimensional de bytes (similarmente a como manejamos la base de datos en
cadenas al principio).
Deseamos insistir en lo que dijimos al principio de esta sección: todos los
archivos en disco se componen de bytes; la manera de agrupar los bytes para
obtener información que tenga sentido depende del software que se use para verlo,
de las máscaras que le apliquemos al archivo. Una vez que terminemos de armar
nuestro archivo con el formato que acabamos de ver, podrán observar el archivo
con alguno de los visores de su sistema operativo y verán que también los primeros
2pn 1q bytes podrı́an tratar de interpretarlos como caracteres ASCII, no como
variables de Java; por supuesto que si hacen esto la mayorı́a de estos caracteres
no se podrán ver en pantalla (por ejemplo, el 0 binario) o aparecerán caracteres
que no guardan ninguna relación con lo que ustedes esperarı́an ver.
10.9 Escritura y lectura de campos que no son cadenas 500
6 s t a t i c f i n a l i n t AGREGA = 1 ,
......
15 LEERREGS = 9 ,
16 GUARDARREGS = 1 0 ;
......
128 p u b l i c i n t daMenu ( B u f f e r e d R e a d e r cons , L i s t a C u r s o miCurso )
129 throws I O E x c e p t i o n {
......
141 DataInputStream a r c h i v o R e g s I n = n u l l ;
142 DataOutputStream a r c h i v o R e g s O u t = n u l l ;
......
156 + "(9)\ tLeer de archivo binario \n"
157 + "(A)\ tGuardar en archivo binario \n"
......
170 o p c i o n = " 0123456789 AB" . i n d e x O f ( s o p c i o n ) ;
......
Nuevamente optamos por declarar los flujos necesarios dentro del método que
maneja el menú. La razón de esto es que estas opciones se pueden elegir en cual-
quier momento y más de una vez, en cada ocasión con flujos fı́sicos distintos, por lo
que hacerlos globales a la clase o, peor aún, al uso de la clase, amarrarı́a a utilizar
únicamente el flujo determinado antes de empezar, cuando existe la posibilidad
de que no se elija esta opción o que, como ya mencionamos, se desee hacer varias
copias de l;a información. En las lı́neas 15, 16 156 a 170 simplemente agregamos
dos opciones al menú, y los mecanismos para manejarlas – posponemos por el
momento el desarrollo de la opción correspondiente dentro del switch –. La decla-
ración de los flujos la hacemos en las lı́neas 141 y 142. Tanto en este caso como en
503 Entrada y salida
134 Reader a r c h i v o I n = n u l l ;
135 Writer archivoOut = null ;
390 try {
391 s h o r t tam = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
392 tamanhos = new s h o r t [ tam + 1 ] ;
393 tamanhos [ 0 ] = tam ;
394 f o r ( i n t i = 1 ; i <= tam ; i ++)
395 tamanhos [ i ] = a r c h i v o R e g s I n . r e a d S h o r t ( ) ;
396 s h o r t maxt = 0 ;
397 f o r ( i n t i = 0 ; i <= tamanhos [ 0 ] ; i ++)
398 maxt =( s h o r t ) ( Math . max ( maxt , tamanhos [ i ] ) ) ;
399 bCadena = new byte [ maxt ] ;
400 w h i l e ( a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) > 0 ) {
401 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
402 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
403 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
404 switch ( i ) {
405 case 2 :
406 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
407 break ;
408 case 3 :
409 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
410 break ;
411 case 4 :
412 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
413 break ;
414 default :
415 break ;
416 } // end o f s w i t c h ( i )
417 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
418 i f ( miCurso == n u l l ) {
419 System . o u t . p r i n t l n ( "No existe miCurso " ) ;
420 throw new N u l l P o i n t e r E x c e p t i o n ( " Uuups " ) ;
421 } // end o f i f ( miCurso == n u l l )
422 miCurso . a g r e g a E s t F i n a l
423 ( new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ) ;
424 } // end o f w h i l e ( a r c h i v o R e g s I n . r e a d ( bCadena
425 } catch ( I O E x c e p t i o n e ) {
426 System . e r r . p r i n t l n ( " Error de I/O" ) ;
427 throw e ;
428 } // end o f t r y c a t c h
429 finally {
430 try {
431 archivoRegsIn . close ();
432 } catch ( I O E x c e p t i o n e ) {
433 System . e r r . p r i n t l n ( "No se pido cerrar el archivo " ) ;
434 } // end o f t r y c a t c h
435 } // end o f f i n a l l y
436 r e t u r n LEERREGS ;
505 Entrada y salida
Como se puede observar, las dos opciones son prácticamente paralelas, excepto
que cuando una escribe la otra lee. Pasemos a revisar primero la opción de escri-
tura, que serı́a el orden en que programarı́amos para probar nuestra aplicación.
10.9 Escritura y lectura de campos que no son cadenas 506
En las lı́neas 443 a 450 se verifica que la lista en memoria no esté vacı́a. De ser
ası́ se procede a obtener el descriptor de los campos (el encabezado del archivo
binario) en un arreglo de enteros pequeños (short); si la lista está vacı́a se sale del
menú lanzando una excepción, que es atrapada desde el método principal (main)
de la aplicación.
Se procede a escribir el encabezado del archivo binario en las lı́neas 452 a 455
como lo indica el diagrama del algoritmo, utilizando para ello el método writeShort
de la clase DataOutputStream – ver documentación de la clase en las páginas 10.9
y 10.9 –. Una vez hecho esto se procede a escribir cada uno de los registros de la
base de datos, como un arreglo de bytes, con cada campo ocupando el número de
bytes que indica el encabezado del archivo – en las lı́neas 459 y 460 se ajusta el
campo a su tamaño agregando blancos y en la lı́nea 463 se escribe utilizando el
método writeBytes de la clase DataOutputStream, que convierte una cadena a un
arreglo de bytes –. Para escribir toda la lista seguimos nuestro algoritmo usual
que recorre listas.
Comparemos ahora el algoritmo con el código del listado 10.7. La parte que
corresponde a abrir el archivo y localizarlo se encuentra en las lı́neas 380 a 389.
En esta opción sı́ procesamos por separado la excepción que nos pueda dar la
localización del archivo binario del que el usuario solicita leer porque es posible que
no exista dicho archivo. Por eso, en lugar de la tradicional excepción IOException,
acá tratamos de atrapar una excepción que nos indica que no se encontró el
archivo. Igual que en el caso anterior, sin embargo, salimos con una excepción del
método, pues tenemos que regresar a solicitar otra opción.
De manera similar a como lo hicimos para escribir, construimos un flujo de la
clase DataInputStream y le pasamos como argumento un objeto de la clase FileIn-
509 Entrada y salida
518 try {
519 System . o u t . p r i n t ( " Ahora dime el numero de registro (0.."
520 + ( f i l e S i z e 1 ) + ") -->" ) ;
521 subcad = cons . r e a d L i n e ( ) ;
522 numR = 0 ;
523 f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++) {
524 numR = numR∗10 + s u b c a d . c h a r A t ( i ) ’0’ ;
525 } // end o f f o r ( i n t i = 0 ; i < s u b c a d . l e n g t h ( ) ; i ++)
526 i f ( numR < 0 | | numR >= f i l e S i z e ) {
527 System . o u t . p r i n t l n ( "Hay menos de " + numR
528 + ". Del 0 al "
529 + ( fileSize 1));
530 r e t u r n LEEREGISTROK ;
531 }
532 } catch ( I O E x c e p t i o n e ) {
533 System . o u t . p r i n t l n ( " Error al leer n’umero de registro " ) ;
534 } // end o f t r y c a t c h
535 // S a l t a r e l numero de b y t e s r e q u e r i d o s p a r a u b i c a r s e
536 // en e l r e g i s t r o s o l i c i t a d o .
537 i n t a S a l t a r = (numR 1 ) ∗ tamR ;
538 i n t pude = 0 ;
539 try {
540 // S a l t a r b y t e s
541 pude = a r c h i v o R e g s I n . s k i p B y t e s ( a S a l t a r ) ;
542 // S i e s que hubo l o s s u f i c i e n t e s
543 i f ( pude >= a S a l t a r ) {
544 bCadena = new byte [ tamR ] ;
545 // Leemos e l r e g i s t r o s o l i c i t a d o .
546 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) ;
547 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
548 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
549 a r c h i v o R e g s I n . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
550 switch ( i ) {
551 case 2 :
552 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
553 break ;
554 case 3 :
555 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
556 break ;
557 case 4 :
558 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
559 break ;
560 default :
561 break ;
562 } // end o f s w i t c h ( i )
563 } // end o f f o r ( i n t i = 1 ; i <= tamanhos [ 0 ] ; i ++)
10.9 Escritura y lectura de campos que no son cadenas 512
554 // Se arma un o b j e t o de l a c l a s e E s t u d i a n t e
555 E s t u d i a n t e nuevo = new E s t u d i a n t e ( nombre , c u e n t a ,
556 carrera , clave );
557 System . o u t . p r i n t l n ( nuevo . d a R e g i s t r o ( ) ) ;
558 } // end o f i f ( pude >= a S a l t a r )
559 } catch ( I O E x c e p t i o n e ) {
560 System . o u t . p r i n t l n ( "Hubo error " ) ;
561 } // end o f t r y c a t c h
562 r e t u r n LEEREGISTROK ;
domAccessFile. Hay que tener presente que aunque esta clase maneja archivos en
disco no hereda de ningún flujo (stream) o de lector/escritor (Reader/Writer), sino
que hereda directamente de Object, aunque sı́ se encuentra en el paquete java.io.
Los ejemplares de esta clase proveen tanto lectura como escritura en el mis-
mo objeto. en general podemos pensar en un archivo de acceso directo como un
arreglo en disco, donde cada elemento del arreglo es un registro y al cual quere-
mos tener acceso directamente a cualquiera de los registros sin seguir un orden
predeterminado.
Con un archivo de este tipo tenemos siempre asociado un apuntador de archivo
(en adelante simplemente apuntador), que se encarga de indicar en cada momento
a partir de donde se va a realizar la siguiente lectura/escritura. Si el apuntador
está al final del archivo y viene una orden de escritura, el archivo simplemente
se extiende (el arreglo crece); si estando al final del archivo viene una orden de
lectura, la máquina virtual lanzará una excepción EOFException. Si se intenta
realizar alguna operación después de que el archivo fue cerrado, se lanzará una
IOException. Se tiene un método que se encarga de mover al apuntador, lo que
consigue que la siguen te lectura/escritura se lleve a cabo a partir de la posición a
la que se movió el apuntador. Estas posiciones son absolutas en términos de bytes.
Este tipo de archivos se pueden usar para lectura, escritura o lectura/escritura,
dependiendo de qué se indique al construir los ejemplares.
Si bien no hereda de ninguna de las clases Stream, como ya dijimos, implemen-
ta a las interfaces DataOutput, DataInput y Closeable. Como se pueden imaginar,
las dos primeras también son implementadas por DataOutputStream y DataInputS-
tream, por lo que tendremos prácticamente los mismos métodos que ya conocemos
de estas dos últimas clases, todos en una única clase. Revisaremos únicamente
aquellos métodos que no conocemos todavı́a, dando por sentado que contamos
con los métodos para leer y escribir de DataInputStream y DataOutputStream.
Con esto ya tenemos las herramientas necesarias para acceder al disco con
acceso directo.
Figura 10.15 Algoritmo para agregar registros desde archivo de acceso directo
$
'
' Pedir nombre de archivo
'
'
'
' Abrir archivo para#lectura
'
'
'
'
'
'
'
Leer número de tamanhos[0] Ð Primer short
'
'
'
' campos del archivo
'
'
'
'
'
' $ $
'
' ' '
' &Leer siguiente
'
'
' Calcular tamaño & Sumar tamanhos[i]
'
' short
'
' de registro % i=1,. . . , tamanhos[0] '
' %
'
' Sumarlo
'
'
'
'
'
'
'
' $ $
'
' '
'
' 'Obtener del usuario el '
' 'Leer cadena de
'
'
' '
' &
'
' '
' número del registro consola
'
' '
'
'
' '
' '
' Procesar dı́gito
'
' '
' solicitado '
%
'
Agregar registros ' '
' mientras haya
& '
'
'
'
desde archivo de '
'
'
' '
' $
acceso directo ' '
'
'
'
'
'
' Calcular posición en
'
&pos Ð
'
'
'
'
'
'
'
'
'
' bytes '
tamR numR
'
'
'
'
'
'
'
'
%
|
encabezado |
' Procesar registro '
'
' &
Colocar el apuntador en esa posición
'
'
'
'
'
' (mientras se den) ''
'
' '
' $
'
' '
' '
'
' '
'
' 'Leer nombre
'
'
' ' &
'
' '
' Leer registro desde Leer cuenta
'
' '
'
'
' '
' disco '
' Leer carrera
'
' '
' '
%
'
' '
'
'
' '
' Leer clave
'
' '
'
'
' '
'
'
' '
' $
'
' '
' '
'
' '
' &Construir registro
'
' '
' Agregar a lista
'
' '
' nuevo
'
% '
% registro leı́do '
%
Agregar a lista
517 Entrada y salida
Código 10.9 Lectura del nombre del archivo y apertura del mismo case LEERDIRECTO (1/2)
Código 10.9 Lectura del nombre del archivo y apertura del mismo case LEERDIRECTO (2/2)
Una vez que calculamos el tamaño del encabezado (numBytes), el tamaño del
registro (tamR) y el número de registros lógicos en el archivo (fileSize) procedemos
a iterar, pidiéndole al usuario el número de registro, tantos como desee, del registro
que desea leer y agregar a la lista en memoria. Esto se lleva a cabo en las lı́neas
642 a 667 en el listado 10.10 en la página opuesta.
519 Entrada y salida
Elegimos leer del usuario una cadena, para evitar errores de lectura en caso
de que el usuario no proporcione un entero – lı́nea 650. Calculamos el entero
correspondiente usando la regla de Horner, que proporciona una manera sencilla,
de izquierda a derecha, de calcular un polinomio. Supongamos que tenemos un
polinomio
cn xn cn1 xn1 . . . c0
Podemos pensar en un número en base b como un polinomio donde x b y
tenemos la restricción de que 0 ¤ ci b. En este caso para saber el valor en base
10 evaluamos el polinomio. La manera fácil y costosa de hacerlo es calculando
cada una de las potencias de b para proceder después a multiplicar ci por bi .
¸
n
P px q ci xi
i 0
Pero como acabamos de mencionar, esta es una manera costosa y poco elegante
10.9 Escritura y lectura de campos que no son cadenas 520
P px q c 0 x pc 1 x pc 2 xp. . .qq . . .q
lo que nos permite evaluar el polinomio sin calcular previamente las potencias de
b. Por ejemplo, el número base 10 8725 lo podemos expresar como el polinomio
Pero como tenemos que calcular de adentro hacia afuera, el orden de las opera-
ciones es el siguiente:
ppp8 10q 7q 10 2q 10 5
8 10 80 7
87 10 870 2
872 10 8720 5
8725
lo que permite leer los dı́gitos de izquierda a derecha e ir realizando las multipli-
caciones y sumas necesarias. La ventaja de esta regla es que cuando leemos el 8,
por ejemplo, no tenemos que saber la posición que ocupa, sino simplemente que
es el que está más a la izquierda. Dependiendo de cuántos dı́gitos se encuentren a
su derecha va a ser el número de veces que multipliquemos por 10, y por lo tanto
la potencia de 10 que le corresponde. Este algoritmo se encuentra codificado en
las lı́neas 655 a 658.
En las lı́neas 651 a 654 verificamos que el usuario no esté proporcionando un
número negativo (que empieza con ’-’). Si es ası́, damos por terminada la sucesión
de enteros para elegir registros.
Quisiéramos insistir en que no importa si el flujo es secuencial o de acceso
directo, una lectura se hace siempre a partir de la posición en la que se encuentra
el flujo. Si se acaba de abrir esta posición es la primera – la cero (0) –. Conforme se
hacen lecturas o escrituras el flujo o archivo va avanzando; en los flujos secuenciales
de entrada, mediante el método skip se puede avanzar sin usar los bytes saltados,
pero siempre hacia adelante. En cambio, en los archivos de acceso directo se cuenta
521 Entrada y salida
con el comando seek que es capaz de ubicar la siguiente lectura o escritura a partir
de la posición dada como argumento, colocando el apuntador de archivo en esa
posición.
En el listado 10.11 se encuentra el código que corresponde a la ubicación del
apuntador del archivo, frente al primer byte del registro solicitado por el usuario
en la iteración actual.
Código 10.11 Posicionamiento del apuntador del archivo y lectura case LEERDIRECTO
687 try {
688 // S a l t a r e l numero de b y t e s r e q u e r i d o s p a r a u b i c a r s e
689 // en e l r e g i s t r o s o l i c i t a d o .
690 a S a l t a r = numR∗tamR + numBytes ;
691 archivoRndm . s e e k ( a S a l t a r ) ;
692 bCadena = new byte [ tamR ] ;
693 // Leemos e l r e g i s t r o s o l i c i t a d o .
694 archivoRndm . r e a d ( bCadena , 0 , tamanhos [ 1 ] ) ;
695 nombre = new S t r i n g ( bCadena , 0 , tamanhos [ 1 ] ) ;
696 f o r ( i n t i = 2 ; i <= tamanhos [ 0 ] ; i ++) {
697 archivoRndm . r e a d ( bCadena , 0 , tamanhos [ i ] ) ;
698 switch ( i ) {
699 case 2 :
700 c u e n t a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
701 break ;
702 case 3 :
703 c a r r e r a = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
704 break ;
705 case 4 :
706 c l a v e = new S t r i n g ( bCadena , 0 , tamanhos [ i ] ) ;
707 break ;
708 default :
709 break ;
710 } // end o f s w i t c h ( i )
711 } // end o f f o r ( i n t i = 1 ;
712 // Se arma un o b j e t o de l a c l a s e E s t u d i a n t e
713 E s t u d i a n t e nuevo = new E s t u d i a n t e
714 ( nombre , c u e n t a , c a r r e r a , c l a v e ) ;
715 i f ( miCurso == n u l l ) {
716 System . o u t . p r i n t l n ( "No existe Curso " ) ;
717 throw new N u l l P o i n t e r E x c e p t i o n ( " Uuups " ) ;
718 } // end o f i f ( miCurso == n u l l )
719 miCurso . a g r e g a E s t F i n a l
720 ( new E s t u d i a n t e ( nombre , c u e n t a , c a r r e r a , c l a v e ) ) ;
721 } catch ( I O E x c e p t i o n e ) {
722 System . o u t . p r i n t l n ( "Hubo error en LEERDIRECTO " ) ;
723 } // end o f t r y c a t c h
724 } // end w h i l e s e p i d a n r e g i s t r o s
10.9 Escritura y lectura de campos que no son cadenas 522
La lista que acabamos de dar dista mucho de ser exhaustiva, pero contiene la
suficiente información para poder cumplir con nuestro objetivo, que consiste en
serializar y deserializar la base de datos. Terminemos de reunir las herramientas
que requerimos mostrando la definición de la clase ObjectOutputStream.
1 import j a v a . i o . ∗ ;
2 /∗ ∗
3 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula
4 ∗ l a l i s t a de un c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s
5 ∗ n o r m a l e s de una b a s e de d a t o s y f u n c i o n a m e d i a n t e un Menú
6 ∗/
7 c l a s s I n f o E s t u d i a n t e S e r i a l implements D a E s t u d i a n t e , S e r i a l i z a b l e {
......
16 /∗ ∗
17 ∗ C o n s t r u c t o r s i n p a r á m e t r o s
18 ∗/
6
Reescribimos estas dos clases agregando al nombre Serial para mantener intacto el trabajo
que realizamos con anterioridad.
10.10 Lectura y escritura de objetos 534
19 public I n f o E s t u d i a n t e S e r i a l () {
20 nombre = c a r r e r a = c u e n t a = n u l l ;
21 }
22 /∗ ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
23 ∗ l o s campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas ,
24 ∗ m i e n t r a s que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́
25 ∗ p o r punto y coma .
26 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
27 ∗ cada uno de l o s campos que s e van a l l e n a r .
28 ∗ @ r e t u r n I n f o E s t u d i a n t e una r e f e r e n c i a a una l i s t a
29 ∗/
30 p u b l i c I n f o E s t u d i a n t e S e r i a l ( S t r i n g nmbre , S t r i n g c nt a ,
31 String crrera ) {
32 nombre = nmbre . t r i m ( ) ;
33 cuenta = cnta . trim ( ) ;
34 carrera = crre ra . trim ( ) ;
35 }
......
1 import j a v a . i o . ∗ ;
2
3 // i m p o r t i c c 1 . i n t e r f a z . C o n s o l a ;
4 /∗ ∗
5 ∗ Base de d a t o s , a b a s e de l i s t a s de r e g i s t r o s , que emula l a l i s t a de un
6 ∗ c u r s o de l i c e n c i a t u r a . T i e n e l a s o p c i o n e s n o r m a l e s de una b a s e de
7 ∗ d a t o s y f u n c i o n a m e d i a n t e un Menú
8 ∗/
9 c l a s s E s t u d i a n t e S e r i a l extends I n f o E s t u d i a n t e S e r i a l
10 implements S e r i a l i z a b l e {
......
13 /∗ ∗ C o n s t r u c t o r s i n p a r á m e t r o s ∗/
14 public Estud i an teS er i al () {
15 super ( ) ;
16 clave = null ;
17 siguiente = null ;
18 s h o r t [ ] tam = { ( s h o r t ) ( tamanhos [ 0 ] + 1 ) ,
19 tamanhos [ 1 ] , tamanhos [ 2 ] , tamanhos [ 3 ] , 2 0 } ;
535 Entrada y salida
20 tamanhos = tam ;
21 }
22 /∗ ∗ C o n s t r u c t o r a p a r t i r de d a t o s de un e s t u d i a n t e .
23 ∗ l o s campos v i e n e n s e p a r a d o s e n t r e sı́ p o r comas ,
24 ∗ m i e n t r a s que l o s r e g i s t r o s v i e n e n s e p a r a d o s e n t r e sı́
25 ∗ p o r punto y coma .
26 ∗ @param S t r i n g , S t r i n g , S t r i n g , S t r i n g l o s v a l o r e s p a r a
27 ∗ cada uno de l o s campos que s e van a l l e n a r .
28 ∗ @ r e t u r n E s t u d i a n t e una r e f e r e n c i a a una l i s t a
29 ∗/
30 p u b l i c E s t u d i a n t e S e r i a l ( S t r i n g nmbre , S t r i n g cn ta ,
31 String crrera , String clve ) {
50 public Estud i an teS er i al daSiguiente () {
51 return s i g u i e n t e ;
52 }
53 /∗ ∗ A c t u a l i z a e l campo s i g u i e n t e .
54 ∗ @params E s t u d i a n t e l a r e f e r e n c i a que s e va a a s i g n a r .
55 ∗/
56 public void p o n S i g u i e n t e ( E s t u d i a n t e S e r i a l s i g ) {
57 siguiente = sig ;
58 }
......
99 /∗ ∗
100 ∗ C o n v i e r t e a un e s t u d i a n t e en un e s t u d i a n t e s e r i a l , s i m p l e m e n t e
101 ∗ c o p i a n d o l o s campos c o r r e s p o n d i e n t e s .
102 ∗ @param nuevo e l E s t u d i a n t e .
103 ∗ @ r e t u r n s Un E s t u d i a n t e S e r i a l con e l mismo c o n t e n i d o que nuevo .
104 ∗/
10.10 Lectura y escritura de objetos 536
105 p u b l i c v o i d d a E s t u d i a n t e S e r i a l ( E s t u d i a n t e nuevo ) {
106 nombre = nuevo . daNombre ( ) ;
107 c u e n t a = nuevo . daCuenta ( ) ;
108 c a r r e r a = nuevo . d a C a r r e r a ( ) ;
109 c l a v e = nuevo . d a C l a v e ( ) ;
110 }
111 /∗ ∗
112 ∗ C o n v i e r t e a un E s t u d i a n t e S e r i a l en un E s t u d i a n t e c o n s t r u y e n d o
113 ∗ uno de ’ e s t o s .
114 ∗ @param e l e s t u d i a n t e s e r i a l .
115 ∗ @ r e t u r n s un E s t u d i a n t e .
116 ∗/
117 public Estudiante daEstudiante () {
118 S t r i n g nmbre = t h i s . daNombre ( ) ;
119 S t r i n g c n t a = t h i s . daCuenta ( ) ;
120 String cr rera = this . daCarrera ( ) ;
121 S t r i n g c l v e = th is . daClave ( ) ;
122 r e t u r n new E s t u d i a n t e ( nmbre , c n ta , c r r e r a , c l v e ) ;
123 }
......
219 case LEEROBJETOS :
220 m e n s a j e += "de dónde vas a leer objetos " ;
221 break ;
222 case GUARDAROBJETOS :
223 m e n s a j e += "en dónde vas a escribir objetos " ;
224 break ;
......
537 Entrada y salida
Con esto ya estamos listos para llenar los casos que nos ocupan. El algoritmo
para la lectura de los registros se muestra en la figura 10.17 y como se puede ob-
servar es prácticamente idéntico al de leer registros de un archivo directo, excepto
que por el hecho de leer objetos la máquina virtual se encarga de interpretarlos.
Figura 10.17 Algoritmo para la lectura de objetos
$
'
' Abrir el archivo
'
' $
'
' '
& &Leer el registro actual
Cargar registros
Lectura de Objetos
'
' (mientras haya) '%
'
' Ingresarlo a la base de datos
'
'
%
Cerrar el archivo
$ #
'
' Abrir el archivo
'
' Inicio
'
' Colocarse al principio de la lista
'
'
'
'
'
'
'
& $
'
' Convertir al estudiante actual
Escritura de Objetos '
&
'
' Tomar registro en estudiante serial
'
'
'
' (mientras haya) ' Escribirlo al flujo de objetos
'
' '
'
'
' %
'
' Tomar como actual al siguiente
'
%
Cerrar el archivo
984 } catch ( I O E x c e p t i o n e ) {
985 yaNoHay = t r u e ;
986 } // end o f t r y c a t c h
987 i f ( yaNoHay ) {
988 continue ;
989 } // end o f i f ( a r c h v o O b j e t o s I n . a v a i l a b l e > 0 )
990 nuevo = n u e v o S r l . d a E s t u d i a n t e ( ) ;
991 miCurso . a g r e g a E s t O r d e n ( nuevo ) ;
992 } // end o f w h i l e ( ( nombre = a r c h i v o I n . r e a d L i n e ( ) . . .
993 } // end o f t r y
994 catch ( C l a s s N o t F o u n d E x c e p t i o n e ) {
995 System . o u t . p r i n t l n ( "Se está leyendo el archivo equivocado " ) ;
996 } // end o f c a t c h
997 catch ( I n v a l i d C l a s s E x c e p t i o n e ) {
998 System . o u t . p r i n t l n ( "La clase no permite ser serializada " ) ;
999 } // end o f c a t c h
1000 catch ( S t r e a m C o r r u p t e d E x c e p t i o n e ) {
1001 System . o u t . p r i n t l n ( "La descripción del objeto "
1002 + "es inconsistente " ) ;
1003 } // end o f c a t c h
1004 catch ( O p t i o n a l D a t a E x c e p t i o n e ) {
1005 System . o u t . p r i n t l n ( "No se encontraron objetos sino "
1006 + " datos directos " ) ;
1007 } // end o f c a t c h
1008 catch ( I O E x c e p t i o n e ) {
1009 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
1010 } // end o f t r y c a t c h
1011 catch ( E x c e p t i o n e ) {
1012 System . o u t . p r i n t l n ( "No alcanzaron los datos " ) ;
1013 } // end o f c a t c h
1014 finally {
1015 i f ( a r c h v o O b j e t o s I n != n u l l ) {
1016 try {
1017 archvoObjetosIn . close ( ) ;
1018 } catch ( I O E x c e p t i o n e ) {
1019 System . e r r . p r i n t l n ( "No pude cerrar el"
1020 + " archivo de lectura " ) ;
1021 } // end o f t r y c a t c h
1022 } // end o f i f ( a r c h i v o I n != n u l l )
1023 } // end o f f i n a l l y
1024 r e t u r n LEEROBJETOS ;
1025
1026 case GUARDAROBJETOS :
1027 try {
1028 s A r c h i v o = pideNombreArch ( cons , GUARDAROBJETOS ) ;
1029 archvoObjetosOut = null ;
10.10 Lectura y escritura de objetos 540
1030 a r c h v o O b j e t o s O u t = new O b j e c t O u t p u t S t r e a m
1031 ( new F i l e O u t p u t S t r e a m ( s A r c h i v o ) ) ;
1032 System . o u t . p r i n t l n ( "Abrı́ el archivo " ) ;
1033 i f ( a r c h v o O b j e t o s O u t == n u l l ) {
1034 System . o u t . p r i n t l n ( "el archivo NO está abierto !" ) ;
1035 } // end o f i f ( a r c h v o O b j e t o s O u t == n u l l )
1036 l i s t a = ( ( E s t u d i a n t e ) miCurso . d a L i s t a ( ) ) ;
1037 int contador = 0;
1038 w h i l e ( l i s t a != n u l l ) {
1039 n u e v o S r l = new E s t u d i a n t e S e r i a l ( ) ;
1040 nuevoSrl . daEstudianteSerial ( l i s t a ) ;
1041 archvoObjetosOut . writeObject ( nuevoSrl ) ;
1042 l i s t a = l i s t a . daSiguiente ();
1043 } // end o f w h i l e ( l i s t a != n u l l )
1044 } catch ( S e c u r i t y E x c e p t i o n e ) {
1045 System . o u t . p r i n t l n ( "Se violaron condiciones de seguridad " ) ;
1046 } // end o f c a t c h
1047 catch ( I O E x c e p t i o n e ) {
1048 System . e r r . p r i n t l n ( "No pude abrir archivo " ) ;
1049 } // end o f t r y c a t c h
1050 finally {
1051 try {
1052 i f ( a r c h v o O b j e t o s O u t != n u l l ) {
1053 archvoObjetosOut . c l o s e ( ) ;
1054 } // end o f i f ( a r c h i v o O u t != n u l l )
1055 } catch ( I O E x c e p t i o n e ) {
1056 System . e r r . p r i n t l n ( "No pude cerrar el archivo " ) ;
1057 } // end o f t r y c a t c h
1058 } // end o f f i n a l l y
1059 r e t u r n GUARDAROBJETOS ;
ción de entrada y salida que se va a alcanzar cuando ya no pueda leer del flujo.
Por ello, colocamos estas lı́neas en un bloque try–catch – 981 a 986 que atrape la
excepción y simplemente prenda una variable para avisar que ya no hay datos.
En las lı́neas 987 a 989 se detecta que ya se alcanzó el fin de archivo (o que no
se pudo leer) y se regresa al encabezado del while, para evitar tratar de procesar
datos erróneos.
Finalmente, en las lı́neas 990 y 991 se obtiene un objeto Estudiante a partir del
que se leyó y se agrega a la lista.
Las cláusulas catch externas a la lectura y que corresponden únicamente al
caso de lectura – lı́neas 994 a 1013 simplemente proporcionan un mensaje de error
de qué es lo que sucedió, lo mismo que las cláusulas catch del caso de escritura a
flujo de objetos – lı́neas 1044 a 1049 –.
En ambos casos que nos ocupan tenemos una cláusula finally que se encarga de
cerrar el archivo, preguntando antes si es que el archivo fue abierto adecuadamente
– lı́neas 1015 a 1022 en lectura y 1050 a 1058 en escritura – que además tiene
que estar, nuevamente, en un bloque try-catch para el caso en que se lance una
excepción.
Es interesante ver con un visor de texto en ASCII un archivo creado como
flujo de objetos ya que se ve, de alguna manera, la gran cantidad de información
que se encuentra adicional a los datos mismos. Esto se debe a que con cada
objeto se tiene que describir al mismo, lo que sucede ya que en un mismo flujo de
objetos podemos escribir objetos de distintas clases y al leerlos, automáticamente
se carga la descripción que trae consigo el objeto. Es por eso que la lectura siempre
es de un Object y se tiene que hacer un cast para obtener el objeto que creemos
estamos leyendo. Para “decodificar” un objeto de un flujo del que no sabemos a
qué clase pertenece, podemos utilizar la clase Class que proporciona toda clase de
herramientas para ello.
10.11 Colofón