Beruflich Dokumente
Kultur Dokumente
de datos
Jordi Àlvarez Canal
PID_00161994
Módulo 1
© FUOC • PID_00161994 • Módulo 1 Tipos abstractos de datos
Índice
Introducción ............................................................................................ 5
Objetivos ................................................................................................... 7
1. Contenedores ...................................................................................... 9
1.1. Dualidad entre especificación/implementación ............................ 9
1.2. Implicaciones matemáticas de los TAD .......................................... 11
1.2.1. TAD como conjunto de valores .......................................... 11
1.2.2. Semántica de un TAD .......................................................... 12
1.2.3. Especificación de un TAD ................................................... 13
1.3. Tipos abstractos de datos en Java ................................................... 14
1.3.1. Bibliotecas en Java ............................................................... 15
1.3.2. Dualidad entre especificación/implementación
en Java ................................................................................. 16
Resumen .................................................................................................... 57
Solucionario ............................................................................................. 61
Glosario ..................................................................................................... 61
Bibliografía .............................................................................................. 62
Anexo ......................................................................................................... 63
© FUOC • PID_00161994 • Módulo 1 5 Tipos abstractos de datos
Introducción
En primer lugar, desarrollaremos las nociones de contenedor y de tipo abstracto de A lo largo del texto utilizaremos
indistintamente los términos
datos (TAD). La primera es una abstracción que nos permite almacenar un grupo contenedor y colección.
de elementos y realizar las operaciones necesarias para gestionar este grupo: inser-
tar nuevos elementos, borrar existentes o consultar el estado del grupo mismo de
elementos (qué elementos hay...). Esta noción tiene una importancia práctica ca-
pital en el mundo del desarrollo de software, ya que la posibilidad de definir gru-
pos de elementos de diferentes tipos y su gestión es necesaria en cualquier
programa de una mínima complejidad.
La noción de TAD se remonta a comienzos de los años setenta, mucho antes TAD es la sigla de
tipo abstracto de datos.
de la aparición de los lenguajes de programación más populares de la actuali-
dad. De hecho, la noción de TAD es completamente independiente del len-
guaje de programación utilizado. Cada lenguaje dispone de un conjunto de
formalismos y construcciones que permiten adaptarse mejor o peor para repre-
sentar la noción de TAD. Como en esta asignatura se utiliza Java, veremos cómo
podemos definir TAD con las herramientas ofrecidas por este lenguaje.
Como hemos comentado, los TAD ofrecen dos aspectos diferentes: la imple-
mentación y el uso. En esta dualidad, ¿cómo definimos la frontera entre lo que
hace el TAD y lo que ha de hacer su usuario (típicamente un programador de
aplicaciones)? La especificación de un TAD es precisamente eso: la definición
de su comportamiento de manera que el usuario pueda programar la aplica-
ción de modo coherente con el comportamiento del TAD.
Con todo esto, habremos presentado el marco teórico con el que trabajaremos en
la asignatura. A continuación, pondremos en común todos estos elementos por
medio de un ejemplo de colección, que presentaremos en el mismo formato es-
tándar que utilizaremos para las colecciones de los módulos siguientes.
Objetivos
1. Contenedores
Esta asignatura versa especialmente sobre cómo representar y gestionar coleccio- En los lenguajes de orientación
a objetos, representaremos un
contenedor con una clase.
nes de datos. Cualquier aplicación mínimamente compleja necesita almacenar En la asignatura Diseño y programación
orientada a objetos ya habéis utilizado las
colecciones de elementos. Por tanto, resulta muy útil disponer de una biblioteca clases java.util.Vector y java.util.ArrayList
para almacenar colecciones.
de contenedores que pueda ser utilizada por cualquier aplicación que hayamos
de desarrollar.
Una vez que la aplicación ya está funcionando, es usual que los requisitos
evolucionen con el tiempo. Por tanto, la aplicación se ha de adaptar a los
© FUOC • PID_00161994 • Módulo 1 10 Tipos abstractos de datos
nuevos requisitos. Estos nuevos requisitos pueden provocar que las estrate-
gias que se utilizaban hasta aquel momento para almacenar colecciones de
objetos dejen de ser óptimas, y sea conveniente sustituirlas por otras. Este
cambio se debería poder realizar fácilmente, y debería tener el menor impac-
to posible en la aplicación.
Por este motivo, y por otros que ya iremos desgranando, cuando desar-
rollemos una aplicación, y ésta haya de utilizar colecciones, es impor-
tante que el código de la aplicación esté tan desvinculado como sea
posible de las estrategias concretas que hemos elegido para almacenar
colecciones de objetos.
Para llevar a cabo esta desvinculación, cada colección dispondrá de dos partes
bien diferenciadas:
• La especificación, que está compuesta por las signaturas de los métodos Recordad de Fundamentos de
programación que la precondición
(los servicios o funcionalidades que se ofrecen) y la descripción de cada (o poscondición) se expresa con un
predicado booleano.
Desde el punto de vista conceptual, la especificación de una colección nos dice Esta dualidad entre especificación e
implementación es equivalente a la de
“qué hace la colección” (qué comportamiento tiene); mientras que su imple- especificación-algoritmo que se muestra
en la asignatura Fundamentos de
programación.
mentación nos dice “cómo lo hace” (cómo está implementado este comporta-
miento).
Del mismo modo que podemos tener distintos algoritmos que cumplan
una misma especificación, aquí podremos tener diferentes implemen-
taciones que proporcionen una misma especificación.
© FUOC • PID_00161994 • Módulo 1 11 Tipos abstractos de datos
Un objeto que es de tipo Nat puede tener el valor 0, o bien el valor 1, o bien
El tipo Nat
el valor 2, etc. En cambio, este objeto nunca podrá tener el valor 33,1, ni el
En el caso del tipo Nat,
valor ‘a’, ni el valor ‘casa’. Es decir, el conjunto de valores asociado al tipo sabemos que el conjunto de
valores asociados es infinito.
Nat es {0, 1, 2, 3, 4, ...}. No ha de ser necesariamente
así siempre. Podemos definir
TAD con un conjunto de valo-
Del mismo modo, un objeto que es del tipo correspondiente a un TAD T cual- res finito.
quiera podrá tomar un conjunto de valores determinado por el TAD T mismo: Imaginad, por ejemplo, un
TAD para valores booleanos.
T1, T2, T3...
Sobre un objeto de tipo Nat podemos realizar una serie de operaciones. Así El formalismo que utilizaremos para
definir un TAD y sus operaciones se
muestra en el subapartado 1.2.3
pues, podemos incrementarlo o decrementarlo en una unidad, sumarle o res- de esta asignatura.
tarle una cantidad, etc. La aplicación de cada una de estas operaciones pro-
ducirá una variación en el valor del objeto. Esto es, aplicar una operación
sobre un objeto de tipo Nat que tiene un valor determinado puede hacer (de-
pendiendo de la definición de la operación) que el valor de este objeto se
transforme en otro, que necesariamente ha de estar asociado a Nat. De este
modo, por ejemplo, si incrementamos un objeto que tiene el valor 56, este
objeto pasará a tener el valor 57.
El conjunto de valores que puede tomar una instancia de un TAD son única-
mente símbolos, pero, de hecho, no tienen ningún significado. Necesitamos
alguna manera más de atribuir un significado o semántica a cada uno de estos
valores. Con el significado podemos, por ejemplo en el caso de los naturales,
saber que el número 2 es 1 + 1, o el doble de 1. En cambio, sin significado, úni-
camente tendríamos los símbolos 2 y 1.
Para dotar de significado al conjunto de valores que puede tomar una instan-
cia de un TAD, la teoría de los tipos abstractos de datos usa la especificación al-
gebraica. En este texto no trataremos con detenimiento este tema, pero sí que
creemos interesante exponerlo brevemente de manera intuitiva, lo que hare-
mos a continuación.
¿Os habéis parado alguna vez a pensar de qué modo se define la semántica
de los números enteros? Si habéis estudiado álgebra, probablemente lo ha-
yáis debido hacer. Los números enteros, junto con las operaciones de suma
y producto, son un anillo conmutativo y unitario. Esto significa que cum-
plen el conjunto de propiedades de un anillo que sea conmutativo y unita-
rio. Estas propiedades están definidas mediante un conjunto de ecuaciones
en las que intervienen las operaciones (en este caso suma y producto) y va-
riables que representan instancias de enteros.
Así, por ejemplo, una de las propiedades de un anillo es la propiedad asociati- Si tenéis curiosidad por el resto
de propiedades o queréis ampliar el
va respecto del producto, expresada por la ecuación: x · (y · z) (x · y) · z. tema, podéis recurrir a cualquier libro
de álgebra que tengáis a mano.
Todo esto permite definir completamente la semántica de un TAD. Ahora bien, Lecturas
complementarias
para hacerlo, es necesario utilizar un formalismo matemático que no forma par-
te del objetivo de esta asignatura. Así que aquí nos limitaremos a utilizar espe- Si queréis ampliar el tema de
la especificación algebraica y
cificaciones informales, tal como se ve en el subapartado siguiente. la semántica de un TAD,
podéis revisar las obras
siguientes:
X. Franch (2001). Estructuras
de datos. Especificación, diseño
1.2.3. Especificación de un TAD e implementación (4.ª ed.).
Barcelona: Edicions UPC.
Disponible en línea en:
<www.edicionsupc.es>.
Para caracterizar o definir un TAD, hay que proporcionar su especificación.
R. Peña Marí (2000). Diseño
de programas. Formalismo y
abstracción (2.ª ed.).
Prentice Hall.
Advertid que en el subapartado anterior se dice explícitamente que la especi-
ficación de un TAD determina el conjunto de valores. Por tanto, a partir de la
especificación seríamos capaces de obtener este conjunto de valores.
TAD Nat {
@pre cierto
@post El natural construido corresponde al número natural 0
Nat();
a) Del mismo modo que sucede con las clases en Java, las operaciones creado-
ras o constructoras tienen el mismo nombre del TAD. En el ejemplo anterior,
disponemos de una operación de creación, Nat(), que crea un objeto de tipo
natural con valor 0.
b) Cada operación del TAD va acompañada por dos etiquetas o tags (@pre y
@post) semejantes a las que utiliza el Javadoc y a las anotaciones del Java 5. Me-
diante estas etiquetas dotamos a cada método de especificación.
Hemos optado por prescindir de un lenguaje formal en la especificación de los La sintaxis habitual de los TAD
presenta muchas diferencias
TAD con el objetivo de evitar toda la maquinaria matemática que sería necesaria respecto de la que utilizamos
aquí, más enfocada hacia la fa-
si hubiéramos de trabajar con la especificación algebraica de TAD. Como ya he- miliarización con una sintaxis
muy parecida a la que usare-
mos comentado, no es el objetivo de este curso estudiar con detenimiento las im- mos en el resto de la asignatura
plicaciones matemáticas de los TAD ni trabajar con ellas; nuestro objetivo es cuando mezclemos los TAD
con el mundo Java. Podéis en-
trasladar al lenguaje de programación que utilizamos (Java) algunas propiedades contrar la sintaxis original y
una profundización en las no-
de los TAD que nos serán muy útiles: la dualidad entre especificación e implemen- ciones matemáticas de los
TAD en las obras de X. Franch
tación, así como la ocultación de la implementación; y, por tanto, poder utilizar (1999) y R. Peña Marí (2000).
un TAD únicamente haciendo referencia a su especificación.
En este subapartado trataremos con un poco más de detalle dos aspectos muy
útiles en relación con los tipos abstractos de datos en programación Java: las
© FUOC • PID_00161994 • Módulo 1 15 Tipos abstractos de datos
Así, por ejemplo, utilizando la biblioteca de clases que nos proporciona el len-
guaje Java, podemos construir fácilmente un programa que nos muestre por
la salida estándar la hora y el día en curso:
HoraActual.java
package uoc.ei.ejemplos.modulo1;
import java.util.Date;
public class HoraActual {
public static void main(String[] args) {
Date ahora = new Date();
System.out.println(ahora);
}
}
La variable CLASSPATH
Asignación del valor
de CLASSPATH
Para poder utilizar una biblioteca (que no sea la propia de Java), debéis incluir el fichero
.jar correspondiente en la variable de entorno del sistema operativo denominada CLAS- Para dar un valor a la variable
SPATH. Así pues, si la variable CLASSPATH es igual a .;c:\ei\tad.jar significará que cuando CLASSPATH, se ha de llevar a
compilemos un programa en Java (y también cuando lo ejecutemos), tenemos acceso, cabo desde el sistema operati-
vo. Cada versión de sistema
primero, a las clases que están situadas en el subárbol de directorios que cuelga del direc- operativo tiene su propio
torio actual (la parte correspondiente al ‘.’); y, en segundo lugar, a todas las clases que modo de hacerlo.
contiene el fichero c:\ei\tad.jar.
Especificación
Como uno de los objetivos principales de la especificación es documentar el Recordad, de Diseño y programación
orientada a objetos, que el término
comportamiento de los métodos correspondientes, habitualmente veremos Javadoc se refiere al formato estándar
para comentar clases Java.
Nat.java
En la definición de la interfaz Nat se
ha omitido expresamente cualquier
package uoc.ei.ejemplos.modulo1; referencia a la especificación del TAD, que
se tratará en el apartado 2 de este módulo.
public interface Nat {
/** Este método incrementa el valor del natural. */
void suc();
Implementación
Una vez definida la interfaz del TAD, podemos proporcionarle una imple-
mentación definiendo una clase que proporcione la definición de cada uno
de los métodos definidos en la interfaz. A continuaión tenéis una implemen-
tación del TAD Nat.
© FUOC • PID_00161994 • Módulo 1 18 Tipos abstractos de datos
NatiImpl.java
package uoc.ei.ejemplos.modulo1;
Como podéis comprobar, la implementación de Nat es totalmente directa. Esto es así por-
que el tipo int de Java ya dispone de operaciones equivalentes a las que estamos intentando
definir en el TAD Nat. Por tanto, únicamente se tratará de utilizar las operaciones homólo-
gas del tipo int. De hecho, esto nos indica que la definición del TAD Nat en Java no nos
resultará útil en realidad. La especificación y la implementación aquí del TAD Nat está úni-
camente justificada por motivos didácticos y tiene el objetivo de introducir, mediante un
ejemplo sencillo, todos los conceptos que utilizaremos con TAD a partir de ahora.
NatbImpl.java
package uoc.ei.ejemplos.modulo1;
implementaciones proporcionadas en NatiImpl se limitan a utilizar los ope- En el módulo “Complejidad al-
gorítmica” aprenderemos a
radores del tipo int de Java, todas las operaciones de NatbImpl contienen calcular de manera rigurosa lo
que tarda un método en ejecu-
una llamada al método privado buscarUltimaPosicion, en el que se hace una
tarse. Lo haremos únicamente
búsqueda en el vector de booleanos. a partir de las instrucciones
que aparecen en la implemen-
tación del método. Al resulta-
• El espacio ocupado por una instancia de NatiImpl (un atributo de tipo int) es do lo denominaremos coste
temporal del método, y nos
más pequeño que el espacio ocupado por una instancia de NatbImpl (un vector permitirá elegir de una manera
objetiva, de entre distintos al-
de booleanos).
goritmos o métodos, aquel
que sea más eficiente.
Todos estos factores son muy importantes a la hora de elegir una implementación
u otra. Si nos ponemos sólo en el papel de usuario de un TAD, nos interesarán so-
bre todo los dos últimos puntos: la eficiencia temporal y la eficiencia espacial.
Fibonacci.java
package uoc.ei.ejemplos.modulo1;
int i = 0;
while (i<n) {
fibn = fibn2;
fibn2 = fibn3;
fibn3 = crearNat();
fibn3.sumarCantidad(fibn);
fibn3.sumarCantidad(fibn2);
i++;
}
System.out.println("El número es: "+fibn);
}
}
© FUOC • PID_00161994 • Módulo 1 23 Tipos abstractos de datos
Al final del apartado anterior, hemos dejado de lado todo lo referente a la es-
pecificación de manera expresa. ¿Qué sucede si se ejecuta uno de los métodos
ofrecidos por la interfaz del TAD Nat en una situación que no está prevista en
el TAD? Esto podría producirse por distintos motivos. Por ejemplo, el usuario
de la aplicación final (que es usuaria de nuestro TAD) puede introducir en ella
un dato erróneo. O bien, una situación también común, la aplicación final tie-
ne algún defecto que hace que se llame a algún método de un TAD en una si-
tuación anormal.
El programa siguiente genera un número natural –que inicialmente tendrá el Observad que la precondición
de pred (podéis ver el
valor 0– y ejecuta el método pred(). El predecesor de 0 no existe, y ninguna de subapartado 1.2) no se cumple
si el natural es 0.
las implementaciones que hemos visto está preparada para tratar esta situación.
PruebaErrorNat.java
package uoc.ei.ejemplos.modulo1.defensivo;
import uoc.ei.ejemplos.modulo1.*;
Según la especificación que hemos dado del TAD Nat tal como aparece en
Precondición
el apartado 1.2, el comportamiento de la operación pred del TAD Nat en el de un algoritmo
caso de que el natural sea 0 no está definido. Es decir, la operación no está Recordad que, tal como estudi-
asteis en Fundamentos de pro-
preparada para recibir un 0 como parámetro y el resultado devuelto no está gramación, en un algoritmo
(en nuestro caso el correspon-
definido, ya que la poscondición no se ha de cumplir necesariamente en diente a una operación de un
esta situación. Si probamos el programa anterior con NatiImpl, veremos có- TAD), su precondición deter-
minará si una llamada es cor-
mo la salida estándar muestra el resultado –1, que no es un número natural. recta o no. El algoritmo
únicamente es responsable de
Si, en cambio, usamos NatbImpl, la ejecución se parará y se generará una tratar aquellas situaciones en
las que la precondición se
ArrayIndexOutOfBoundException, porque la operación pred estará intentan- cumple.
do acceder a la posición –1 del vector de booleanos.
En este caso, la precondición de la operación pred del TAD Nat nos informa de
que PruebaErrorNat es responsable de garantizar que n no es el natural 0 cuan-
do se hace la llamada a pred. Pero la precondición es un predicado que, de mo-
mento, no comprobamos en ningún lugar al hacer la llamada. ¿Tiene sentido,
© FUOC • PID_00161994 • Módulo 1 24 Tipos abstractos de datos
por tanto, mantener la precondición como está? ¿No hará esto que el TAD sea
poco robusto?
Elegir entre una u otra opción es una decisión que habremos de tomar siempre
que diseñemos un nuevo TAD, o, de hecho, cualquier clase.
NatiDefensivoImpl.java
package uoc.ei.ejemplos.modulo1.defensivo;
...
public void pred() throws ExcepcionNat {
if (nat==0)
throw new ExcepcionNat(“No existe el predecesor de 0”);
nat--;
}
...
© FUOC • PID_00161994 • Módulo 1 25 Tipos abstractos de datos
Si hacemos responsables a los usuarios del TAD de garantizar que los paráme- Responsabilidad
tros recibidos son correctos, nos ahorraremos la programación defensiva. En y verificación
cambio, esta opción es menos sólida frente a posibles usos incorrectos. Por este Hacer responsables a los usua-
rios de un TAD de garantizar su
motivo, el uso de la programación defensiva podría parecer en un primer mo- uso adecuado no significa que
éstos hayan de hacer las
mento más adecuada. Pero esto no es exactamente así: el uso de la programa- comprobaciones defensivas.
ción defensiva tiene también desventajas importantes. Si bien es cierto que
puede hacer el código más sólido, su presencia hace también al código más
complejo y difícil de leer, en definitiva, menos mantenible.
Las dos opciones también se pueden combinar. Esto puede suceder cuando en
un mismo método se dan distintas situaciones de error. Para algunas, nos puede
interesar que el propio TAD tenga la responsabilidad de comprobarlas, mientras
que para otras nos puede interesar que sea el cliente quien garantice al TAD que
la situación es la correcta para realizar la operación.
2.2. Contratos
El contrato entre una clase y sus usuarios no es nada más que la espe-
cificación de esta clase, que representa el acuerdo formal entre la clase
y sus usuarios, en la que quedan definidos los derechos y obligaciones
de cada parte.
2) Como consecuencia, “garantiza” a los usuarios de la clase que, una vez eje-
cutado el método, se cumplirá la poscondición.
Del mismo modo que los contratos legales que podemos firmar (por ejemplo,
para comprar un piso u obtener un crédito), el contrato que nos ocupa vincula
a dos partes: por un lado, a una clase y, por otro, a sus usuarios. También, como
en los contratos legales, define una serie de compromisos y garantías para cada
una de las partes. Evidentemente, los compromisos de una parte son las garan-
tías de la otra.
Ejemplo
En la especificación del TAD Nat vista en el subapartado 1.2, se establece que la precondición
de la operació pred es que el valor del número natural representado sea mayor que 0: “X > 0”;
y la poscondición, que el valor del número natural representado sea “X – 1”. El programa
PruebaErrorNat que hemos visto al comienzo del apartado 2 llama al método pred con
© FUOC • PID_00161994 • Módulo 1 28 Tipos abstractos de datos
un natural que tiene valor 0. Por tanto, PruebaErrorNat está incumpliendo su parte del con-
trato. Este incumplimiento hace que ninguna de las implementaciones de Nat esté obligada
a garantizar que la poscondición de pred se cumpla.
Una implementación de un TAD utilizará un conjunto de atributos para repre- Podéis ver NatbImpl en el
subapartado 1.3.2 de este módulo.
sentar el valor concreto que toma la instancia del TAD. A este conjunto de atri-
butos lo denominaremos representación del TAD. Así, en el caso de NatbImpl, la
representación consta de un único atributo, que es un vector de booleano. Observación
2, 3, ...)– son los de la representación de NatbImpl que los representa. Así, por
ejemplo, el natural 5 se puede representar de la siguiente manera:
Ahora bien, a menudo nos encontraremos con casos en los que representacio-
nes diferentes representan el mismo valor del TAD. Observad, por ejemplo, los
dos vectores siguientes. Ambos son representaciones diferentes. En cambio,
ambos representan al natural 5.
Adicionalmente, también hay situaciones en las que las representaciones no Podéis ver el TAD Nat en el
subapartado 1.2.3 de este módulo.
equivalen a ningún valor del TAD. Por ejemplo, fijémonos ahora en la otra im-
plementación del TAD Nat que hemos visto: NatiImpl. En esta implementación,
la representación está constituida por el atributo entero nat. La representación
nat = –3 es una representación posible, ya que nat es un int y, como tal, puede
tomar valores negativos. En cambio, la mencionada representación no se corres-
ponde con ningún valor del TAD Nat.
El invariante de la representación es una noción especialmente útil para propor- En estos materiales, no trabajaremos
más este tema. Sí que os lo
encontraréis, pero de manera implícita,
cionar la especificación (o contrato) de un TAD para una implementación concre- en las especificaciones de las
implementaciones de la biblioteca
ta. Para cada una de las operaciones del TAD, el invariante de la representación se de colecciones de la asignatura.
La mayoría de cursos y libros de texto existentes en el área de los TAD y las estruc-
turas de datos no establecen una vinculación directa entre el marco conceptual
que acabamos de definir y la parte más práctica, correspondiente al desarrollo y
uso de implementaciones de TAD. Es decir, no suele haber ningún vínculo entre
la especificación formal del comportamiento de un TAD y su implementación.
Así pues, todas las implementaciones de TAD que introducimos en esta asig-
Precondiciones
natura irán acompañadas de su contrato. Definiremos el contrato de una im- y poscondiciones en Java
plementación de la siguiente manera: A pesar de que en Java repre-
sentamos las precondiciones y
poscondiciones como expre-
• Expresaremos la precondición y la poscondición de cada uno de los métodos siones booleanas, debéis tener
presente que no forman parte
de la clase, así como el invariante de su representación mediante expresiones del algoritmo y que no tienen
ningún efecto en él.
booleanas, utilizando una extensión de la misma sintaxis proporcionada por
el lenguaje Java (más adelante veremos en qué consiste esta extensión).
Observad que en los párrafos anteriores hemos hablado de contratos para las
implementaciones de los TAD, y no para los TAD en sí mismos, representados
en Java mediante interfaces. Siempre que podamos, intentaremos proporcio-
nar el contrato de manera genérica para la interfaz que representa al TAD. No
obstante, esto no siempre será posible y, en ocasiones, estaremos limitados a
proporcionar únicamente contratos para las implementaciones.
No siempre será posible acceder a este estado desde la misma interfaz; depende-
rá de si ésta ofrece las operaciones adecuadas de consulta (algo que no siempre
será así). En cambio, hacerlo desde la implementación concreta misma de un
TAD siempre será posible, ya que los atributos que definen este estado están de-
finidos en la misma implementación (y claramente tenemos acceso a ellos).
Nat.java
package uoc.ei.ejemplos.introduccion;
/** Método consultor que devuelve el valor del natural como un entero.
* @post $return == consultar() && consultar() == $old(consultar())
* @return el valor del objeto. */
int consultar();
}
Como podemos ver, para cada método de la interfaz Nat definimos la precon-
El método consultar
dición y poscondición mediante las etiquetas @pre y @post. Los valores aso-
Observad que para el caso de
ciados a estas etiquetas son expresiones booleanas de Java con algún elemento la interfaz Nat es posible pro-
porcionar un contrato para la
adicional. Veamos cuáles son estos elementos adicionales: interfaz, ya que disponemos
del método consultar, que nos
permite consultar el ”estado”
• $old(expresión). Permite hacer referencia al valor de la expresión justo antes del natural (de hecho su valor).
Si el TAD no dispusiera de este
de ejecutar el método. La expresión se puede corresponder con toda la re- método, sólo podríamos defi-
presentación del TAD (en este caso, encontraríamos $old(this)), con un atri- nir los contratos en cada una
de sus implementaciones.
buto del TAD (sólo cuando se trata de una implementación, en el ejemplo
anterior este uso no es posible porque una interfaz no tiene atributos) o,
incluso, con cualquier expresión evaluable antes de ejecutar el método (por
ejemplo, $old(consultar()).
Ha quedado claro que –como en cualquier otro contrato– el incumplimiento Podéis ver las repercusiones del
incumplimiento del contrato en el
subapartado 2.2.1 de este módulo.
del contrato por parte de los usuarios de un TAD exime al TAD de cumplir su
parte del contrato. Sin embargo, en el proceso de desarrollo de una aplicación,
es normal que se cometan errores. Por lo tanto, puede ocurrir que la aplicación
usuaria de un TAD incumpla su parte de contrato en alguna de las llamadas a
métodos de este TAD. Por motivos obvios, nos interesará detectar esta situa-
ción y localizar el error lo antes posible.
Tal y como también hemos comentado, en una situación ideal querríamos te- En los recursos electrónicos
de la asignatura, encontraréis
documentación sobre el sistema de
ner la capacidad de detectar los incumplimientos de contrato sin haber de DBC que emplearemos en este curso.
Una vez se haya acabado la fase de desarrollo, si queremos, podemos sustituir Encontraréis una explicación más
detallada sobre el funcionamiento
de la herramienta de DBC en los
el fichero jar generado por la herramienta de DBC por otro jar que podemos recursos electrónicos del aula.
Debéis tener en cuenta, sin embargo, que no será necesario que utilicéis esta
herramienta en este curso; ya que, en principio, vosotros no deberéis desarro-
llar ninguna biblioteca. A lo largo de todo el curso, trabajaréis con la biblioteca
de TAD de la asignatura, de la que tendréis dos versiones diferentes (es decir,
dos ficheros jar): una generada con la herramienta de DBC, que hará las com-
probaciones de los contratos, y otra generada con las herramientas estándar
del entorno Java, que no comprobará ningún contrato.
© FUOC • PID_00161994 • Módulo 1 35 Tipos abstractos de datos
Decimos que los elementos del conjunto no están ordenados porque no nos Elección de colecciones
importa el orden en el que se hayan añadido, ni tampoco cualquier otro orden
Cuando conozcáis más tipos
que podamos definir entre los elementos del conjunto (por ejemplo, en el caso de colecciones y estéis en
proceso de resolución de un
de los números naturales, el orden ‘<’ dentro de los mismos números natura- problema, un paso muy impor-
tante de este proceso será ele-
les). Con eso queremos decir, por ejemplo, que {1, 5, 8} y {8, 1, 5} son en rea- gir las colecciones que nos
lidad el mismo conjunto. permitan representar de mane-
ra adecuada el dominio del
problema. Dado que en un
Por otro lado, decimos que un conjunto es un grupo de elementos sin repeti- conjunto no se establece nin-
gún orden entre los elementos
ción, ya que los elementos sólo aparecen en él una vez. Es decir, a partir de un que lo forman, la colección
Conjunto no nos servirá para
elemento e y de un conjunto C, sólo existen dos situaciones posibles: o bien e representar, por ejemplo, una
cola de personas que están es-
pertenece a C o bien no pertenece a éste. perando para ser atendidas en
una gestoria. En cambio, sí que
nos servirá para representar el
conjunto de gestiones que se
pueden realizar en la gestoría.
3.1. Operaciones
Una vez que tenemos conceptualmente claro en qué consiste y cuáles son las
propiedades de la colección que hemos elegido de ejemplo, veremos cuáles son
las operaciones que nos interesará proporcionar sobre la colección Conjunto:
Representación
de colecciones
colección Conjunto es Ésta es la forma en la que pre-
sentaremos habitualmente
cada una de las colecciones
constructor() que vayan apareciendo a lo lar-
go de los módulos. Para cada
Crea un conjunto vacío. operación que se pueda efec-
tuar sobre la colección tene-
@pre cierto.
mos: su signatura en negrita,
@post Devuelve un conjunto sin ningún elemento ($this es el conjunto una breve explicación, y la pre-
condición y poscondición, que
vacío). estarán precedidas de las eti-
quetas @pre y @post. En el
caso de las colecciones (que
se corresponderán a interfaces
void insertar(Object elem) Java), la precondición y la
poscondición se expresarán
Añade un elemento al conjunto. Si el elemento ya existe, no hace nada. habitualmente de manera
@pre cierto. informal.
Las operaciones previas corresponden a las operaciones más elementales que Encontraréis la interfaz Java
correspondiente a esta colección
en los ejemplos disponibles en
podemos realizar sobre los conjuntos. Aunque las colecciones como tales no tie- los recursos electrónicos
de la asignatura (interfaz Conjunto
nen constructor, normalmente les añadiremos operaciones denominadas cons- en el paquete uoc.ei.ejemplos.modulo1.
Conjunto).
La versión de los conjuntos que proporcionamos en este apartado nos permite En el apartado “Ejercicios de
autoevaluación” del módulo,
crear un conjunto vacío, añadirle y borrarle elementos, y consultar si un obje- encontraréis material adicional
relacionado con la ampliación de las
funcionalidades de los conjuntos
to concreto le pertenece. Sería interesante disponer de operaciones adiciona- presentados en este apartado.
les. Por ejemplo, poder unir dos conjuntos, saber cuántos elementos tiene, o
poder recorrer los elementos. Por otro lado, en otro módulo, se estudia una
versión mucho más completa de los conjuntos, también con implementacio- En el módulo “El TAD Tabla”, de
nes más eficientes de las que veremos a continuación. a esta asignatura, se estudia una
versión mucho más completa de los
conjuntos.
Vayamos pues al primer paso: elegir una representación que nos permita re- La eficiencia se estudia en el módulo
“Complejidad algorítmica” de esta
asignatura.
presentar los conjuntos. Normalmente, elegiremos la representación en la que
© FUOC • PID_00161994 • Módulo 1 37 Tipos abstractos de datos
Las representaciones que utiliza un vector para almacenar los elementos que
constituyen una colección tienen una particularidad: en el momento de la
creación de la colección, es necesario establecer el número máximo de ele-
mentos. El motivo está muy claro: necesitamos definir el tamaño del vector
en el momento de la creación de la colección, y el tamaño es el que determi-
nará, en principio, el número de elementos que podremos guardar como
máximo en esta colección .
Una vez aclaradas las implicaciones de utilizar una representación con vecto-
res, veremos cómo se lleva a cabo la implementación de las operaciones a par-
tir de la representación elegida. En primer lugar, mostremos un esquema
algorítmico de cada una de las operaciones:
• constructor()
Operación de insertar
– Crea el vector de elementos.
– Asigna el número de elementos actual a 0. Observad que en la operación
de insertar, es el usuario quien
ha de garantizar que el conjun-
• void insertar(Object elem) to tiene espacio suficiente para
añadir el elemento. Por tanto,
– Asigna ‘elem’ a la primera posición libre del vector de elementos. la implementación no necesita
realizar ninguna comproba-
– Incrementa el número de elementos actual. ción.
package uoc.ei.ejemplos.modulo1;
public ConjuntoVectorImpl(int n) {
elementos = new Object[n];
numElemsActual = 0;
}
...
public Object borrar(Object elem) {
Object elementoBorrado = null;
int posicion = buscarPosicionElemento(elem);
if (posicion! = -1) {
elementoBorrado = elementos[posicion];
numElemsActual--;
if (posicion<numElemsActual)
elementos[posicion] = elementos[numElemsActual];
// importante para la recogida de basura
elementos[numElemsActual] = null;
}
return elementoBorrado;
}
© FUOC • PID_00161994 • Módulo 1 40 Tipos abstractos de datos
• El constructor.
La explicación que daremos a lo largo de los distintos módulos de esta asigna- Los conceptos básicos para analizar
la eficiencia de un algoritmo se
presentan en el módulo “Complejidad
tura sobre la implementación de las operaciones de una colección irá siempre algorítmica” de esta asignatura.
PruebaConjunto.java
package uoc.ei.ejemplos.modulo1;
...
public class PruebaConjunto {
...
public static void main(String[] args) {
boolean salir = false;
ConjuntoAcotado cjt = new ConjuntoVectorImpl(10);
while (!salir) {
try {
String elem = readLine("Introduce un núm (o \"fin\"): ",System.in);
salir = elem.equalsIgnoreCase("fin");
cjt.insertar(new Integer(Integer.parseInt(elem)));
if (cjt.estaLleno()) {
System.out.println("El conjunto está lleno. No se pueden añadir más
elementos.");
salir = true;
}
} catch (NumberFormatException e) {
System.out.println("El texto introducido no es un número vuélvelo a
probar.");
} catch (IOException e) {
System.out.println("Problema de E/S, saliendo");
salir = true;
}
}
System.out.println("El conjunto resultante es: "+cjt);
}
}
Como podéis apreciar, en primer lugar se crea la colección, y después se le aña- Como siempre, encontraréis
el programa completo como
recurso electrónico en el aula.
den elementos uno a uno hasta que o bien el conjunto está lleno, o bien el
usuario decide no llenarlo más (y entra el valor especial ‘fin’).
• Para la creación de la colección es imprescindible hacer referencia a la im- La mayoría de estos aspectos
ya han sido comentados con
anterioridad. Sin embargo, dada su
plementación de la colección (ConjuntoVectorImpl). Ahora bien, una vez importancia, no está de más recordarlos
una vez ya tenemos una visión más clara
creada, es una buena práctica de programación hacer referencia única- de lo que es un TAD y de su uso.
cesario utilizar la interfaz ConjuntoAcotado, porque la interfaz Conjunto no Queda como razonamiento
para el lector deducir cómo, con
dispone del método estaLleno y, en consecuencia, no nos permitiría realizar las esta instrucción if posterior a la
comprobaciones necesarias para garantizar la precondición del método in- llamada a insertar, se garantiza
que el método insertar no se lla-
sertar. mará nunca cuando el conjunto
esté lleno.
Esto significa que podremos utilizar como elementos de los conjuntos del aparta- Como ya sabéis de Diseño y
programación orientada a objetos, el
do anterior cualquier objeto que sea instancia de Object o de una subclase de ésta. tipo Object es el tipo más general en Java.
Todo el resto de tipos de Java heredan
directa o indirectamente del tipo Object
Es decir, podremos utilizar como elemento cualquier objeto, dejando de lado los (excepto los primitivos, que son int, char,
boolean, float y double).
tipos Java primitivos. Por tanto, los conjuntos del apartado anterior pueden con-
tener instancias de Integer, tal como se muestra en el ejemplo al final del apartado,
pero también podríamos poner en ellos instancias de String, Boolean, o cualquier
clase definida por nosotros mismos. Incluso, nadie nos impide añadir a un con-
junto elementos que sean instancia de Conjunto (y, por tanto, podríamos tener fá-
cilmente conjuntos de conjuntos) tampoco existe ningún impedimento para que
en un mismo conjunto pueda haber elementos de distintas clases. La única res-
tricción impuesta por la firma de Conjunto, y que comprobará el compilador de
Java, es que todos los elementos sean instancia de Object.
A primera vista, ésta puede parecer la situación ideal (porque es la más gene-
ral). Pero en muchas ocasiones –de hecho es lo más habitual– nos interesará
definir conjuntos en los que todos los elementos sean del mismo tipo. En estas
situaciones, sería muy difícil estar seguros de que todos los elementos añadi-
dos a un conjunto son instancias de este tipo concreto.
Esto no se puede hacer con las interfaces y la implementación vistas en el apar- No podríamos aprovechar las
implementaciones existentes
mediante herencia; pero sí utilizando otras
tado anterior, que aceptan elementos del tipo mas general que existe en Java técnicas de la orientación a objetos (podéis
ver el ejercicio de autoevaluación 11).
(Object). Para hacerlo, habría que definir de nuevo las interfaces correspon-
dientes, con una nueva signatura para cada uno de los métodos, y también ha-
bría que proporcionar una nueva implementación. En estas interfaces, habría
que modificar las firmas de los métodos, en las cuales sustituiríamos el tipo
Object de los elementos por el tipo concreto que necesitáramos. En cuanto a la
implementación, como las signaturas de los métodos diferirían de la imple-
mentación del apartado anterior, habría que redefinir las implementaciones
de todos los métodos, sin poder reutilizar ninguna de las existentes.
El concepto de tipos paramétricos, también denominados genéricos, ya se intro- Los tipos paramétricos se comentan
en el subapartado 6.2 del módulo 3
de la asignatura Diseño y programación
duce en la asignatura Diseño y programación orientada a objetos. En este aparta- orientada a objetos, a pesar de que no
se estudian con detenimiento.
do, veremos cómo utilizar tipos paramétricos en Java.
La utilización de tipos paramétricos no resulta muy compleja. En este aparta- Los tipos paramétricos
do, la presentaremos mediante un ejemplo en el que incorporaremos el uso de en Java
tipos paramétricos a la colección Conjunto del apartado anterior. La incorporación de los tipos
paramétricos en Java se ha rea-
lizado en su versión 1.5, dispo-
nible desde bien avanzado
Definiremos y utilizaremos un tipo paramétrico en dos fases: el 2004.
Otros lenguajes como C++,
Eiffel y Ada también disponen
1) Definición del tipo paramétrico en función de uno o más paráme- de tipos paramétricos.
Veamos estas dos fases para la colección Conjunto. En primer lugar definimos
la interfaz Conjunto con un parámetro que corresponde al tipo de los elemen-
tos, tal como se muestra a continuación:
Conjunto.java
package uoc.ei.ejemplos.modulo1.genericos;
• Un vez especificados los parámetros, los podemos utilizar dentro del cuer-
po de la clase o de la interfaz en cualquier momento, como cualquier otro
tipo que tengamos definido. Fijaos cómo las firmas de los métodos de la
interfaz Conjunto son idénticas a las del apartado anterior y han sustitui-
do Object por E.
continuación.
ConjuntoVectorImpl.java
package uoc.ei.ejemplos.modulo1.genericos;
public ConjuntoVectorImpl(int n) {
elementos = (E[])new Object[n];
numElemsActual = 0;
}
...
public E borrar(E elem) {
E elementoBorrado = null;
int posicion = buscarPosicionElemento(elem);
if (posicion! = -1) {
elementoBorrado = elementos[posicion];
numElemsActual--;
if (posicion<numeroElementosActual)
elementos[posicion] = elementos[numeroElementosActual];
}
return elementoBorrado;
}
}
Del uso de los tipos paramétricos en esta implementación hay que remarcar
varias cosas:
b) La aparición del parámetro con diferentes funciones. Por una parte, aparece en
la signatura de los métodos, tal como ya habíamos visto en Conjunto, pero tam-
bién en la definición del atributo elementos, o en la variable local del método bo-
rrar denominada elementoBorrado; y también en la operación de casting del
constructor. No está de más volver a recalcar que podemos utilizar el parámetro
(o parámetros) del mismo modo como podríamos utilizar cualquier otro tipo.
Para acabar, nos queda ver cómo concretamos el tipo paramétrico Conjun-
to<E> en un tipo que podamos instanciar para trabajar con conjuntos, tal
como hemos hecho al final del apartado anterior. Adaptaremos a continua-
ción el ejemplo del apartado anterior para utilizar los conjuntos paramétricos
definidos en este apartado.
PruebaConjunto.java
package uoc.ei.ejemplos.modulo1.genericos;
...
public class PruebaConjunto {
...
public static void main(String[] args) {
System.out.println("Introduce numeros para añadir al conjunto");
boolean salir = false;
ConjuntoAcotado<Integer> cjt = new ConjuntoVectorImpl<Integer>(10);
while (!salir) {
try {
...
cjt.insertar(Integer.parseInt(elem));
...
}
...
}
}
System.out.println("El conjunto resultante es: "+cjt);
}
}
tros por los tipos concretos deseados (en este caso Integer). Fijaos en que
hay dos sustituciones diferentes: por un lado, la del tipo de cjt, que es
ConjuntoAcotado<Integer>; y, por otra, tenemos la clase que se instancia,
que es ConjuntoVectorImpl<Integer>.
Como parte muy importante del material docente del curso disponéis de una
biblioteca de colecciones que se ha diseñado e implementado especialmente
para esta asignatura. Esta biblioteca, a la cual también nos referiremos como bi-
blioteca de TAD de la asignatura, contiene:
• Una jerarquía de TAD con todas las colecciones que iréis viendo a lo largo
del curso. Se ha intentado que la jerarquía sea sencilla pero, a la vez, sufi-
cientemente potente y flexible para que resulte efectiva en un amplio ran-
go de situaciones.
• elementos(): permite aceder a cada uno de los elementos almacenados en la Podéis ver los iteradores
en el módulo “Contenedores
secuenciales” de esta asignatura.
colección mediante una construcción auxiliar denominada Iterador.
Los tipos de contenedor básicos que aparecen a lo largo de los módulos se definen
como interfaces que extienden la interfaz Contenedor directamente. Así, extien-
den directamente la raíz de la jerarquía: ContenedorAcotado, Pila, Cola, Lista, Arbol,
Diccionario y Conjunto. Con el fin de proporcionar una biblioteca sencilla, las úni-
cas interfaces de la biblioteca que representan colecciones serán éstas. Cada una
de estas interfaces proporciona la signatura directamente utilizable de los respec-
tivos TAD (excepto la base, Contenedor y ContenedorAcotado, que define únicamen-
te un aspecto de los contenedores). Podéis ver esta jerarquía en la figura 1.
Figura 1
En las figuras que aparecen a continuación se muestran distintas partes de la Trataremos con detenimiento las
colecciones mencionadas en los
módulos “Contenedores secuenciales”
jerarquía de implementaciones de la biblioteca. En la figura 3 podéis obser- y “Colas con prioridad”,
en el que se estudia con detalle esta
var las implementaciones de los TAD secuenciales: las dos implementaciones implementación concreta.
Figura 2
Por lo que respecta a los árboles, la biblioteca tiene una jerarquía de implementa- Si bien no es tarea de esta
asignatura tratar la reutilización del
código en una jerarquía
ciones moderadamente profunda, motivada principalmente para maximizar la de clases, cuando estudiéis las
implementaciones arbóreas, sería muy
reutilización del código. En el ámbito funcional, nos encontramos con un con- interesante que revisarais el código de la
biblioteca teniendo en cuenta este
aspecto.
junto de TAD más reducido: principalmente árboles binarios, árboles n-arios y ár-
boles ordenados.
© FUOC • PID_00161994 • Módulo 1 51 Tipos abstractos de datos
Figura 3
Así, por ejemplo, el ArbolBinario es una clase abstracta que implementa la in-
terfaz Arbol. En esta clase, se definen el conjunto de métodos de los árboles bi-
© FUOC • PID_00161994 • Módulo 1 52 Tipos abstractos de datos
Una tercera parte de las implementaciones proporcionadas en la biblioteca Los TAD Diccionario y Conjunto, así
como sus implementaciones se
estudian en los módulos “El TAD Tabla”
consiste en implementaciones de las interfaces Diccionario y Conjunto. A dife- y “Árboles de busca”.
Figura 4
© FUOC • PID_00161994 • Módulo 1 53 Tipos abstractos de datos
Figura 5
Aparte de las interfaces y clases que representarán las colecciones y sus imple-
mentaciones, se necesitan también algunos tipos auxiliares que nos permiti-
rán trabajar de una manera homogénea con las colecciones.
© FUOC • PID_00161994 • Módulo 1 54 Tipos abstractos de datos
• Posiciones. Hay colecciones que están organizadas posicionalmente, de El concepto de posición aparece en
las colecciones de secuencias y
también en los árboles, y se trabaja
manera que cada elemento está almacenado en una posición concreta. Este exhaustivamente en el módulo
“Contenedores secuenciales” de esta
tipo de colecciones ofrecen un conjunto de operaciones para trabajar con asignatura.
El TAD Diccionario se estudia entre los módulos “El TAD Tabla” y “Árboles de La memoria secundaria
búsqueda”. Un Diccionario es un TAD que nos permite acceder a un elemento
Este tipo de memoria normal-
a partir de una clave. En el módulo “El TAD Tabla” se presenta el TAD y se mente hace referencia al disco,
y es mucho más lenta que la
estudia una implementación eficiente basada en lo que se denomina tablas memoria primaria. En la imple-
mentación de bases de datos,
de dispersión. En el módulo “Árboles de búsqueda” se estudia otra implemen- se ha de tener en cuenta el fac-
tación basada en árboles. En este módulo se presentan también algunas va- tor de la memoria secundaria,
ya que el tamaño de los datos
riantes de los árboles de búsqueda especialmente diseñadas para almacenarlas hace normalmente inviable te-
nerlos todos a la vez en memo-
en la memoria secundaria (en lugar de en la primaria, como las estructuras que ria primaria.
se habrán presentado hasta entonces). Estas variantes se utilizan en la imple-
mentación de bases de datos.
El módulo “Grafos” presenta distintas versiones del TAD Grafo. Los módulos
anteriores se centran en TAD que permiten representar colecciones de elemen-
tos y que tienen una estructura bastante rígida. El objetivo de un grafo no es
© FUOC • PID_00161994 • Módulo 1 56 Tipos abstractos de datos
tanto representar un conjunto de elementos, sino más bien definir una estructu-
ra en la que participen un conjunto de elementos, sin ninguna otra restricción en
su forma. Este TAD es la base para un conjunto de algoritmos extrapolables a mu-
chas situaciones del mundo real que nos permiten llevar a cabo una diversidad de
cálculos complejos. En esta asignatura, sin embargo, nos centraremos estricta-
mente en temas de representación e implementación de las operaciones bási-
cas del TAD, ya que los algoritmos aplicables sobre grafos se estudian en la
asignatura Grafos y complejidad.
Para acabar, una vez vistos todos los TAD que se estudian en el curso, el mó-
dulo “Bibliotecas de colecciones” explica cómo diseñar nuevos TAD basán-
donos en un conjunto de colecciones básicas (las que habréis visto en los
otros módulos). Aparte de esto, el módulo habla sobre el diseño de bibliote-
cas de colecciones, y analiza los factores que hacen que una biblioteca sea
más cómoda de usar que otra, o más potente. Finalmente, se presentan algu-
nas de las bibliotecas más comúnmente usadas en el mundo del desarrollo
orientado a objetos.
© FUOC • PID_00161994 • Módulo 1 57 Tipos abstractos de datos
Resumen
En este módulo hemos presentado las nociones básicas y el marco teórico que
nos acompañará a lo largo del resto del curso. En primer lugar, hemos estudia-
do la noción de contenedor, un elemento básico en el desarrollo de cualquier
aplicación informática. Seguidamente, hemos visto la noción de TAD, que uti-
lizamos como abstracción para separar la especificación de la implementación
de los contenedores.
Otra de las nociones básicas introducidas en este módulo es la del diseño por
contrato (DBC), que se ha presentado en el apartado 2. La noción de contrato
nos hace razonar sobre qué es responsabilidad del TAD y qué es responsabili-
dad del usuario del TAD. El diseñador del TAD es quien establece el contrato
y quien decide quién asume cada responsabilidad. Dependiendo de las deci-
siones que tome el diseñador, el TAD será más o menos usable, y más o menos
mantenible. Una vez definido, el contrato es inamovible y el usuario del TAD
se ha de adaptar a él.
Ejercicios de autoevaluación
4. Revisad la especificación del TAD Nat del subapartado 1.2.3 y la de la interfaz Nat del
subapartado 2.4. ¿Dicen lo mismo? Razonad las diferencias y el motivo.
5. Incorporad dos nuevas operaciones al TAD Nat: una operación denominada multiplicar
que multiplique el natural por otro natural, y otra operación denominada dividir, que haga
la división entera. Se os pide lo siguiente:
• Definid la signatura de las dos operaciones de manera similar a la de la operación sumar
(con un parámetro de tipo Nat).
• Estableced el contrato para estas dos nuevas operaciones. Pensad en definirlo siguiendo
las recomendaciones del apartado 2. Proporcionad la especificación de las nuevas ope-
raciones del TAD en lenguaje natural.
• Incorporad las dos operaciones a las implementaciones vistas en el texto:
– Definid la especificación.
– Codificad las operaciones en lenguaje Java.
6. Definid un TAD denominado Hora, que represente un momento del día constituido por hora
(0-23), minuto (0-59) y segundo (0-59), con las cuatro operaciones siguientes:
• constructor(int h,int m,int s): crea una hora a partir de tres enteros que representan hora,
minuto y segundo.
• tick: avanza la hora un segundo.
• sumar(Hora h): suma otra hora.
• restar(Hora h): resta otra hora.
La especificación del TAD no se os proporciona de manera expresa.
a) Evaluad diferentes posibilidades a la hora de definir el contrato del TAD, y razonad sus
ventajas y desventajas.
b) Elegid una y proporcionad la especificación del TAD.
c) Definid el TAD en Java.
d) Proponed una representación para el TAD Hora en Java.
e) Proporcionad el invariante de la representación.
f) Especificad (usando el sistema DBC de la asignatura) e implementad el TAD Hora utili-
zando la representación propuesta (si queréis, podéis definir métodos adicionales co-
mo, por ejemplo, toString).
7. Desarrollad una aplicación usuaria del TAD Hora que calcule el día y la hora de llegada de un
vuelo, a partir del día y la hora de salida, el tiempo de vuelo y el número de franjas horarias
que cruza. Por ejemplo, en un vuelo Barcelona-Ciudad de México que sale a las 10 de la ma-
ñana, tarda 14 horas (con escala) y cruza 6 franjas horarias, la aplicación debería dar como re-
sultado las 18 horas.
• ¿Qué problemática veis en el uso del TAD Hora para esta aplicación? ¿Se podría producir
incumplimiento del contrato en alguna situación?
• En caso de que el contrato y/o la signatura del TAD Hora que hayáis diseñado en el ejer-
cicio anterior no se adapte a esta aplicación, haced las modificaciones necesarias y razo-
nad los motivos de éstas.
8. Revisad la documentación Javadoc de las clases del JDK y, más en concreto, la interfaz
Collection y la clase abstracta AbstractCollection:
• Echad un vistazo a la jerarquía de interfaces y clases que heredan de las dos e identificad
la representación Java de, al menos, dos TAD. Es decir, os pedimos que identifiquéis dos
interfaces y, para cada una de ellas, al menos una clase que la implemente.
• Buscad al menos un TAD que tenga más de una implementación.
10. Dad una explicación informal sobre la eficiencia temporal de las tres operaciones del ejer-
cicio 9 (¡después de estudiar el módulo “Complejidad algorítmica” podréis hacer este ra-
zonamiento de una manera totalmente precisa!). Adicionalmente, se os pide:
• Buscad alguna forma de modificar la representación (o sus propiedades) con el objeti-
vo de hacer más eficientes las implementaciones del ejercicio anterior.
• Contestad si después de esta modificación es necesario modificar la especificación del
TAD. En caso afirmativo, decid qué partes.
• Contestad si sucede lo mismo con la especificación de la implementación con el siste-
ma DBC de la asignatura. En caso afirmativo, decid qué partes.
11. Imaginad que no existen los tipos paramétricos en Java y que queremos definir un con-
junto de strings. Además, queremos estar seguros en tiempo de compilación de que todos
los elementos que se añaden al conjunto son del tipo String. Para esto se os pide lo si-
guiente:
• Definid en Java una interfaz denominada ConjuntoString con la signatura adaptada
para forzar a los elementos del conjunto a ser del tipo String.
• Decid si podéis heredar la signatura de Conjunto vista en el apartado 3 y por qué.
• Dad una implementación de esta interfaz. ¿Podéis reaprovechar de alguna manera la
implementación dada en el subapartado 3.2? En caso afirmativo, ¿os puede ayudar al-
guna técnica de la orientación a objetos?
12. Trasladad las implementaciones del ejercicio 9 a la variante de TAD Conjunto que utiliza
tipos paramétricos definida en el apartado 4.
13. Buscad semejanzas entre los TAD que habéis encontrado en el ejercicio 8 y los de la bi-
blioteca de TAD de la asignatura. Guiaos únicamente por la funcionalidad básica del
TAD. Tened en cuenta que TAD conceptualmente semejantes pueden diferir en el núme-
ro de operaciones y la signatura de éstas según los criterios utilizados en el diseño de la
biblioteca de la que forman parte.
16. Revisad el Javadoc de la clase java.util.Comparator del JDK. Proporcionad una implemen-
tación para la implementación del TAD Hora que habéis definido en el ejercicio 7.
© FUOC • PID_00161994 • Módulo 1 61 Tipos abstractos de datos
Solucionario
4. Sí, no podría ser de otra manera. La única diferencia es el formalismo que se utiliza: en
el primer caso se utiliza lenguaje natural y, en el segundo, el sistema DBC de la asignatu-
ra. No siempre será posible especificar los TAD utilizando el sistema DBC de la asignatura.
Las implementaciones, en cambio, sí, ya que siempre dispondremos de la representación,
a la cual podremos hacer referencia en las expresiones Java que el sistema DBC usa.
7. Problemática del uso del TAD Hora: se puede dar la situación de que un avión salga de su
origen un día y llegue el día siguiente a su destino. Si el contrato establecido en el ejerci-
cio 6 responsabiliza al usuario del TAD de hacer que las operaciones ejecutadas no cam-
bien de día, o bien se produciría incumplimiento de contrato, o bien se requeriría un
tratamiento laborioso de la aplicación usuaria. Este tratamiento laborioso replicaría en
parte el comportamiento definido en el TAD Hora, algo no deseable.
Modificaciones necesarias
El contrato se debería modificar de manera que fuera responsabilidad del TAD tratar el
paso de un día a otro. Esto se puede hacer de varias maneras:
a) Ampliando el conjunto de valores representados, es decir, pasando del TAD Hora a un
TAD que podríamos denominar Instante y que tuviera en cuenta también el día, y modi-
ficara su valor si la suma o resta de horas hiciera cambiar de día.
b) Modificando la firma de las operaciones relacionadas del TAD Hora de manera que “se avi-
se” al usuario de alguna manera de que se ha cambiado el día. Por ejemplo, devolviendo un
booleano que sería ‘cierto’ si se cambia de día y, ‘falso’ en caso contrario.
c) Notificando al usuario del TAD la situación excepcional de sobreseimiento mediante
el lanzamiento de una excepción. El usuario del TAD debería capturar la excepción, que
contendría información necesaria sobre la situación excepcional.
Podríamos pensar que éste es un ejemplo en el que la programación defensiva es una bue-
na solución. En realidad, se trata más de un tema funcional en el que la aplicación desa-
rrollada delega el control sobre el cambio de día al propio TAD que se encarga de calcular
las horas, ya que ambas tareas están muy relacionadas.
13. El TAD representado en el JDK mediante la interfaz java.util.List sería conceptualmente pa-
recido al representado por la interfaz de la biblioteca de TAD de la asignatura uoc.ei.tad.Lis-
ta. ¡Advertid, no obstante, que hay muchas diferencias por lo que respecta a la signatura!
Estas diferencias se deben principalmente a la diferencia de criterios usados en el diseño de
ambas bibliotecas (este tema se comenta delladamente en el módulo “Bibliotecas de colec-
ciones”). El TAD representado en el JDK por java.util.Set correspondería a la interfaz
uoc.ei.tad.Conjunt de la biblioteca de colecciones de la asignatura.
Por lo que respecta a las implementaciones, la interfaz Lista tiene dos implemen-
taciones: ListaEncadenada y ListaDoblementeEncadenada. Y Conjunto tiene también dos
implementaciones: ConjuntoAVLImpl y ConjuntoTablaImpl. Todas las implementaciones
están ubicadas también en el paquete uoc.ei.tads.
Glosario
contrato m Definición de las responsabilidades y derechos que tiene un TAD, clase, método
u operación, por una parte, y sus usuarios por la otra.
diseño por contrato m Técnica de diseño basada en la definición de contratos para separar
las responsabilidades de una clase de las de sus usuarios.
tipo paramétrico m Definición de tipo en la que uno o más elementos no se han concre-
tado y permanecen como parámetros.
Bibliografía
Bibliografía básica
Franch, X. (2001). Estructuras de datos. Especificación, diseño e implementación (4.ª ed.). Bar-
celona: Edicions UPC. Disponible en línea en: <www.edicionsupc.es>
Peña Marí, R. (2000). Diseño de programas. Formalismo y abstracción (2.ª ed.). Prentice Hall.
Goodrich, M.; Tamassia, R. (2001). Data structures and algorithms in Java (2.ª ed.). John
Wiley and Sons.
© FUOC • PID_00161994 • Módulo 1 63 Tipos abstractos de datos
Anexo