0% encontró este documento útil (0 votos)
17 vistas27 páginas

Introducción a la Programación Genérica en Java

El documento introduce los conceptos de programación genérica y plantillas en Java. Explica que la programación genérica permite definir algoritmos de forma independiente de los tipos de datos concretos. Luego, describe cómo Java implementa la programación genérica a través de plantillas de clases, las cuales permiten definir clases genéricas cuyos tipos de datos pueden ser especificados más adelante. Finalmente, presenta ejemplos de clases genéricas con uno y dos tipos de datos genéricos, así como el uso de herencia para restringir los tipos gen
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
17 vistas27 páginas

Introducción a la Programación Genérica en Java

El documento introduce los conceptos de programación genérica y plantillas en Java. Explica que la programación genérica permite definir algoritmos de forma independiente de los tipos de datos concretos. Luego, describe cómo Java implementa la programación genérica a través de plantillas de clases, las cuales permiten definir clases genéricas cuyos tipos de datos pueden ser especificados más adelante. Finalmente, presenta ejemplos de clases genéricas con uno y dos tipos de datos genéricos, así como el uso de herencia para restringir los tipos gen
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

3 Programación Genérica

Veremos dos aspectos importantes y novedosos incluidos en Java, la Programación genérica (Plantillas) y
las Colecciones. Estas fueron gradualmente implementadas en la versión 5.0. Inicialmente veremos el
concepto de generalidad y cómo éste es implementado a partir de Templetes. Posteriormente veremos
como es posible extender este concepto a estructuras de datos preestablecidas (Colecciones) que nos
permitan manipular datos homogéneos de una manera más simple como las listas ligadas entre otras.

Introducción
La POO supuso un formidable avance en la construcción, desarrollo y mantenimiento de aplicaciones muy
grandes de herramientas de programación, en las que se estaba en el límite de lo factible con las técnicas
programación tradicional. Sin embargo, algunos teóricos seguían centrando su atención en los algoritmos.
Algo que estaba ahí también desde el principio. Se dieron cuenta que frecuentemente las manipulaciones
contienen un denominador común que se repite bajo apariencias diversas. Por ejemplo: La idea de
ordenación "Sort" se repite infinidad de veces en la programación, aunque los objetos a ordenar y los
criterios de ordenación varíen de un caso a otro. Alrededor de esta idea surgió un nuevo paradigma
denominado programación genérica o parametrizada.

La programación genérica está mucho más centrada en los algoritmos que en los datos y su postulado
fundamental puede sintetizarse, en una palabra: generalización. Significa que, en la medida de lo posible,
los algoritmos deben ser parametrizados al máximo y expresados de la manera más independiente posible
de detalles concretos, permitiendo así que puedan servir para la mayor variedad posible de tipos y
estructuras de datos.

Los expertos consideran que la parametrización de algoritmos supone una aportación a las técnicas de
programación, al menos tan importante, como fue en su momento la introducción del concepto de herencia,
y que permite resolver algunos problemas que la programación estructurada no puede resolver.

Observe que la POO y la programación genérica representan enfoques en cierta forma ortogonales entre si:

• La programación orientada a objetos razona del siguiente modo: Representemos un tipo de dato
genérico (por ejemplo, int) que permita representar objetos con ciertas características comunes (peras y
manzanas, por ejemplo). Definamos también que operaciones pueden aplicarse a este tipo (por
ejemplo, aritméticas) y sus reglas de uso, independientemente que el tipo represente peras o manzanas
en cada caso.

• Por su parte la programación genérica razona lo siguiente: Construyamos un algoritmo genérico (por
ejemplo, ordenamiento), que permita representar algoritmos con ciertas características comunes
(ordenación de cadenas alfanuméricas y vectores, por ejemplo). Definamos también a que tipos
pueden aplicarse este algoritmo y sus reglas de uso, independientemente que el algoritmo represente la
ordenación de cadenas alfanuméricas o vectores.

Desde sus inicios Java ha adoptado conceptos de lenguajes anteriores. Aunque siempre se ha
caracterizado por realizar re-implementaciones de conceptos de programación mas potentes y seguras
debido principalmente que se trata de un lenguaje comercial adoptado por la mayoría de los programadores
de aplicaciones WEB. Sin embargo, la generalidad es un concepto antiguo en lenguajes de programación y
procede desde los años ochentas (Ada 1983).

3.1 Plantillas “Templates” en Java


La generalidad es una propiedad que permite definir una clase o una función sin tener que especificar el
tipo de todos o alguno de sus miembros. Esta propiedad no es imprescindible en un lenguaje de
programación orientado a objetos y ni siquiera es una de sus características. Esta característica de Java
apareció mucho más tarde que el resto del lenguaje, a mediados de la primera década del dos mil.

La utilidad principal de este tipo de clases o funciones es la de agrupar variables cuyo tipo no esté
predeterminado. Así el funcionamiento de una Pila, una Cola, una Lista, un Conjunto, un Diccionario o un
Arreglo es el mismo independientemente del tipo de datos que almacene (int, long, double, char, u
objetos de una clase definida por el usuario). En definitiva, estas clases se definen independientemente del
tipo de variables que vayan a contener y es el usuario de la clase el que debe indicar ese tipo en el
momento de crear un objeto de esa clase.

1
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

Plantillas de Clases
La manera de definir una plantilla de clase es muy similar al resto de los lenguajes orientados a objetos, es
necesario definir un tipo al que denominaremos genérico. Se definirá un parámetro que indicará el tipo o
tipos de datos con los que más adelante se crearán los objetos. A continuación, se presenta un ejemplo
muy simple que utiliza plantillas de clases.

Primero declaramos el tipo de la clase, en este caso será tipo genérico, es decir Tipo, y a continuación se
realiza la declaración e implementación de los elementos que estarán incorporados dentro de la clase. En
este caso definimos una clase con un valor:

class Generico1 < Tipo > {


private Tipo valor;

public Generico1() {
setValor(null);
}

public Tipo getValor() {


return valor;
}

public void setValor( Tipo valor ) {


[Link] = valor;
}

public static void main(String [] args) {


Generico1 < String > x;
Generico1 < Integer > y;
x = new Generico1<String>();
y = new Generico1<Integer>();
[Link]("Hola");
[Link]( 1 );
[Link]([Link]());
[Link]([Link]());
}
}

Para nuestro caso definiremos una variable para manejar el elemento que será insertado en la variable
genérica x e y, en este caso valor será la variable que se adaptara al tipo de dato que sea definido al
momento de su declaración dentro del constructor de manera tal que para el objeto x serán aceptados
únicamente valores del tipo String, mientras que para y se insertaran valores del tipo Integer. Tome en
cuenta que estamos hablando de objetos por lo que no serán aceptados tipos nativos como int. Dentro del
constructor se asigna el espacio necesario, de manera dinámica, para almacenar los elementos de acuerdo
al tipo de dato que se halla asignado en la llamada al constructor desde la función.

Los métodos setValor() y getValor() son advertidos del tipo de dato que ingresara a la función de manera
que se respeta el tipo específico para cada caso. También es posible ingresar valores diferidos dentro de
una misma plantilla de manera que al momento de ser declarados se podrá decidir por el tipo de dato que
ingresará, en el siguiente ejemplo se toma como base tipos diferidos:

class Generico2 < Tipo1, Tipo2 > {

private Tipo1 valor1;


private Tipo2 valor2;

public Generico2() {
this(null, null);
}

public Generico2(Tipo1 v1, Tipo2 v2) {


this.setValor1( v1 );
this.setValor2( v2 );
}
public Tipo1 getValor1() {
return valor1;
}

2
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

public Tipo2 getValor2() {


return valor2;
}
public void setValor1( Tipo1 valor ) {
this.valor1 = valor;
}

public void setValor2( Tipo2 valor ) {


this.valor2 = valor;
}

public static void main(String [] args) {


Generico2 < String, Integer > x;
Generico2 < Integer, Double > y;
x = new Generico2<String, Integer>("Hola", new Integer(4));
y = new Generico2<Integer, Double>();
y.setValor1(new Integer(1) );
y.setValor2(new Double(3.0));
[Link](x.getValor1());
[Link](x.getValor2());
[Link](y.getValor1());
[Link](y.getValor2());
}
}

La herencia es un mecanismo indispensable en la generalidad ya que nos permite definir que el tipo de dato
que ingresará será de alguno en particular de manera que es posible indicar que un tipo de dato
corresponderá a una clase particular, por ejemplo, objetos de tipo Number:

class Generico3 < Tipo extends Number > {


private Tipo valor;

public Generico3() {
setValor(null);
}

public Tipo getValor() {


return valor;
}

public void setValor( Tipo valor ) {


[Link] = valor;
}

public static void main(String [] args) {


Generico3 < Double > x;
Generico3 < Integer > y;
x = new Generico3<Double>();
y = new Generico3<Integer>();
[Link](2.3);
[Link](new Integer(1));
[Link]([Link]() + [Link]());
}
}

La operación:
[Link]([Link]() + [Link]());

Es una operación valida debido a que ambos objetos provienen de un linaje similar que les permite
semánticamente ser sumados.

Es importante recordar que aunque Object es un tipo de dato padre a nivel jerárquico, no se debe de
confundir con un tipo de dato genérico, Object no es un tipo de dato genérico, simplemente existe como
dato puente y es posible usar sus características para enlazarlo con otros tipos de datos, pero es importante
recordar que si enviamos un tipo genérico es probable que lo empleemos para realizar una operación
especifica y en dado caso quizás sea necesario recuperar el objeto a granel (como es) para después usarlo
para otros menesteres.

3
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

En el siguiente ejemplo hacemos una ligera modificación de manera tal que manejamos objetos genéricos
que posteriormente pueden ser recuperados a partir de un objeto de alta jerarquía:

class Generico4 <Tipo extends Number> {


private Object valor;

public Generico4() {
this(null);
}

public Generico4( Tipo valor ){


setValor( valor );
}

public Tipo getValor() {


return valor;
}

public void setValor( Tipo valor ) {


[Link] = valor;
}

public static void main(String [] args) {


Generico4<Double> x;
Generico4<Integer> y;
x = new Generico4<Double>();
y = new Generico4<Integer>(1);
[Link](2.3);
[Link]( [Link]() + [Link]());
}
}

La operación:
[Link]([Link]() + [Link]());
error: bad operand types for binary operator '+'

Es una operación invalida debido a que ambos objetos provienen de un linaje (Object) que les impide
semánticamente ser sumados. Para ello será necesario realizar un cast en ambos objetos para que
podamos extraer el valor real que correspondería:

[Link]( (Double)[Link]() + (Integer)[Link]());

Hasta ahora hemos trabajado con tipos de datos que hasta cierto punto se pueden discretizar e identificar, y
pueden ser de varios tipos, o extensiones de alguno en particular, pero que pasa cuando tratamos de
compararlos.

class Comodin <Tipo extends Number> {


private Tipo valor;

public Comodin() {
this(null);
}

public Comodin( Tipo valor ){


setValor( valor );
}

public Tipo getValor() {


return valor;
}

public void setValor( Tipo valor ) {


[Link] = valor;
}

public boolean igual( Tipo val ) {


return [Link]() == [Link]();
}

4
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

public static void main(String [] args) {


Comodin<Double> x = new Comodin<Double>(3.0);
Comodin<Double> y = new Comodin<Double>(3.0);
[Link]("x e y son el mismo "+ [Link](y));
}
}

En la mayoría de los casos no podrá, inclusive si son del mismo tipo definido, en esos casos es posible
hacer uso de un comodín que nos permita diferenciarlos, pero es necesario que uno le indique dicha
conversión es decir uno es responsable de bajarlos al mismo tipo de manera que puedan ser comparables.

class Comodin<Tipo extends Number>


{
private Tipo valor;

public Comodin() {
this(null);
}

public Comodin( Tipo valor ){


setValor( valor );
}

public Tipo getValor() {


return valor;
}

public void setValor( Tipo valor ) {


[Link] = valor;
}

public boolean igual( Comodin< ? extends Number > val ) {


return [Link]() == [Link]();
}

public boolean equals( Comodin<?> val ) {


return [Link]().doubleValue() == [Link]().doubleValue();
}

public static void main(String [] args)


{
Comodin<Double> x = new Comodin<Double>(3.0);
Comodin<Integer> y = new Comodin<Integer>(3);
[Link]("x e y son el mismo "+ [Link](y));
[Link]("x e y son el mismo "+ [Link](y));
}
}

Un comodín indica que se recibirán tipos de datos derivados de alguna clase en particular, pero uno será el
responsable de indicarlo. El siguiente ejemplo vemos que se reciben tipos de datos derivados, indistintos de
una clase padre en particular de manera que puedan ser comparables, para esto empleamos el indicador
de comodín < ? >, que indicará que el tipo será de alguno en general y estaremos en posibilidad de
compararlos, el método

public boolean igual(Comodin<?> val) {


return [Link]() == [Link]();
}

compara los valores, pero a nivel objeto pues recibe valores diferidos a comparar, pero es incapaz de
decirnos si se trata de el mismo contenido pues los compara a nivel objeto (nos indica si se trata de la
misma referencia en memoria) de manera que indica que son de distinto tipo y además viven en
distintos lugares de memoria.

Una solución alternativa es decirle a la función que los objetos serán llevados a un mismo nivel semántico
de manera que puedan ser comparados.

public boolean equals(Comodin<?> val) {


return [Link]().doubleValue() == [Link]().doubleValue();
}

Es decir, un cast explicito puede corroborar que se trata del mismo valor a nivel semántico.

5
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

Construcción de una Pila usando un arreglo y plantillas


Empleando el mecanismo de parametrización resulta relativamente sencillos comenzar a desarrollar
nuestras propias estructuras de datos, aunque generalmente la primera vez que lo hacemos nos damos
cuenta de que es necesario implementar algún mecanismo de estructuras de datos, ya que la
parametrización resulta mas fácil de esa forma.

Pensemos en el caso de una estructura simple como la Pila en la que empleamos un arreglo de tamaño
estático, generalmente la primera vez que lo plantemos en Java pensamos en construir un arreglo de tipo
parametrizado como se muestra a continuación:

public class PilaG <G> {

private G[] elementos;


private int pivote;

PilaG(int tam){
[Link] = new G [tam];
[Link]=0;
}

public boolean push(G elemento){


if( pivote < [Link]){
elementos[pivote++] = elemento;
return true;
}
return false;
}

public G pop(){
if(pivote <= 0)
return null;
return elementos[--pivote];
}

public G nextPop(){
if(pivote <= 0)
return null;
return elementos[pivote-1];
}

public static void main(String [] args){


PilaG<Character> x = new PilaG(5);
[Link]('1');
[Link]('2');
[Link]([Link]());
[Link]([Link]());

PilaG<Integer> y = new PilaG(5);


[Link](1);
[Link](2);
[Link]([Link]());
[Link]([Link]());
}
}

Los arreglos parametrizados deberán ser modificados con un cast para que puedan funcionar, como se
presentan a continuación:

public class PilaG <G>


{
private G[] elementos;
private int pivote;

PilaG(int tam){
[Link] = (G[]) new Object [tam];
[Link]=0;
}

De manera que el principio mas simple es volver a plantear la solución y pensar en una estructura auto
referenciada como mecanismo de construcción, como se muestra a continuación:

6
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

class Pila<T> {
private Nodo<T> cima; /** Puntero a la cima de la pila */

// class Nodo
static class Nodo<T> {
public T dato;
public Nodo<T> siguiente;
Nodo(T dato) {
[Link] = dato;
[Link] = null;
}
public T getDato(){
return [Link];
}
}
//Metodo constructor por defecto de la clase Pila
public Pila() {
cima = null;
}

public Pila(T dato) {


Nodo<T> nodo = new Nodo<T>(dato);
[Link] = cima;
cima = nodo;
}

public Pila(Pila<T> copia) {


while (![Link]()) {
[Link]([Link]());
[Link]();
}
}

public T getDatoCima() {
return [Link];
}

public boolean estaVacia() {


return (cima == null);
}

public void insertar(T dato) {


Nodo<T> nodo = new Nodo<T>(dato);
[Link] = cima;
cima = nodo;
}

public Nodo<T> extraer() {


Nodo<T> nodo = cima;
if (!estaVacia()) {
cima = [Link];
return cima;
}
else
return null;
}

public static void main(String [] args){


Pila<Integer> x = new Pila<Integer>();
[Link](1);
[Link]("inserto " + [Link]() );
[Link](2);
[Link]("inserto " + [Link]() );
[Link](3);
[Link]("inserto " + [Link]() );
Nodo<Integer> t = [Link]();
[Link]("elemento extraido " + [Link]());
}
}
7
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

Ejercicio de Parametrización
Se deja al lector plantearse el siguiente problema, se cuenta con una lista circular simple la cual maneja un
tipo de dato int, lo que se pide es re-escribirla de manera tal que pueda ser implementado un mecanismo de
parametrización, es decir crear una plantilla de la clase Lista Circular que permita trabajar con plantillas o
datos genéricos. Las Clases Nodo y Lista Circular lucen de la siguiente manera:

class Nodo {}// Clase Nodo

public class ListaCircular // Clase Lista simple circular


{
private Nodo raiz;
private int tamano;

public ListaCircular() {}
public boolean esVacia() {}
public void insertar(T dato) {}
public Nodo extraer() {}
public int getTamano() {}
public void mostrar() {}
}

Además, se requiere que emplee dicha clase para construir un programa que capture la información relativa
a una agenda telefónica, considerando: nombre, dirección y teléfono.

3.2 Plantillas de Funciones


Vamos a suponer que se quiere crear una función que devuelva el mínimo entre dos valores
independientemente de su tipo (se supone que ambos tienen el mismo tipo). Se podría pensar en definir la
función tantas veces como tipos de datos se puedan presentar (Integer, Caracter, Float, Double, etc.).
Aunque esto es posible, éste es un caso ideal para aplicar plantillas de funciones. Esto se puede hacer de la
siguiente manera:

class FuncionGenerica
{
public static <T extends Comparable<T>> T minimo( T a, T b) {
if( [Link](b) < 0 )
return a;
else
return b;
}

public static void main(String [] args) {


Integer euno=10, edos=5;
[Link](minimo(euno, edos));

Character cuno='a', cdos='d';


[Link](minimo(cuno, cdos));

String s1 = "hola" , s2 = "HOLA";


[Link](minimo(s1, s2));
return;
}
}

En ese caso con <T extends Comparable<T>> se está indicando que se trata de una plantilla cuyo
parámetro va a ser el tipo T y que extiende de una clase denominada Comparable que tiene la
particularidad de ser comparable o reducida a su valor semántico mediante el método compareTo. de
manera que los tipos se igualan para que puedan ser comparable.

La ejecución del programa anterior demuestra que el tipo de los argumentos y el valor de retorno de la
función minimo( ) se particularizan en cada caso a los de la llamada. Es obvio también que se producirá un
error si se pasan como argumentos dos variables de distinto tipo, por lo que el usuario de la plantilla de
función debe ser muy cuidadoso en el paso de los argumentos.

8
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

3.3 Colecciones en Java


Java cuenta con estructuras de datos que permiten generar colecciones homogéneas (aplicando herencia y
polimorfismo) de datos que se encuentran organizados de manera consecutiva en memoria. Los arreglos
siempre son una buena solución en casos en los que pretendemos almacenar varios datos para después
procesarlos o realizar alguna manipulación de sus elementos. Desafortunadamente los arreglos no son
siempre el mejor medio para solucionar problemas del manejo de datos, debido principalmente a que se
encuentran muy acotados a un tamaño especifico, esta peculiaridad los hace difíciles de mantener.

Es posible realizar varias acciones para mejorar su desempeño, pero no en todos los casos un arreglo
puede ser la mejor forma de solucionar todos los problemas, debido a estas limitaciones es que en
computación se ha optado por implementar diversas estructuras de datos como Listas, Conjuntos, Colas y
Mapas, que permitan manipular elementos de manera más simple.

Java cuenta con estructuras de datos flexibles y robustas que permiten manipular datos de manera simple,
a estas estructuras se les conoce como Colecciones (Collection), existen varios tipos de colecciones, en
esencia una colección no es una clase, sino que se trata de una interface genérica que define la forma en
como trabajaran en general todas las clases que se deriven de ella y sean clasificadas como tal.

La interfaz Iterator es una de las interfaces


raíz de las clases de colecciones. La interfaz
Collection hereda de Iterable, así que todos
los subtipos de Collection también
implementan la interfaz Iterable.

Iterator<T> it = [Link]();
while ([Link]()) {
T e = [Link]();

[Link]("Objeto:"+e);
}

Las clases de este tipo pueden también


hacer uso del ciclo for-each.

List list = new ArrayList();


for(Object o : list){
//hacer algo con o;
}

La interfaz Collection es una de las


interfaces raíz de esta clase. Aunque no es
posible instanciar de manera directa una
Colección, en su lugar se instancia un
subtipo de Colección.

9
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

Java no incluye una implementación utilizable de la interfaz Collection, así que se tendrá que usar alguno
de los subtipos mencionados. Collection (como cualquier otra interfaz) únicamente define un grupo de
métodos (comportamiento) que compartirán cada uno de los subtipos de dicha interfaz. Lo anterior, hace
posible el ignorar el tipo específico de colección que se está usando y se puede tratar como una colección
genérica.

Sin importar el subtipo de Collection que se esté utilizando, existen algunos métodos comunes para
agregar y quitar elementos de una colección:

• add() agrega el elemento dado a la colección y regresa un valor verdadero (true) si la colección
cambió como resultado del llamado al método add(). Un conjunto (set) por ejemplo, podría no haber
cambiado ya que, si el conjunto ya contenía el elemento, dicho elemento no se agrega de nuevo.

• remove() quita el elemento dado y regresa un valor verdadero si el elemento estaba presente en la
colección y fue removido. Si el elemento no estaba presente, el método remove() regresa falso
(false).

• addAll() agrega todos los elementos encontrados en la colección pasada como argumento al
método. El objeto colección no es agregado, solamente sus elementos. Si en lugar de utilizar
addAll() se hubiera utilizado add(), entonces el objeto colección se habría agregado y no sus
elementos individualmente. Algunos subtipos de colección permiten que el mismo elemento sea
agregado varias veces (listas), otros no (conjuntos).

• removeAll() elimina todos los elementos encontrados en la colección pasada como argumento al
método. Si la colección pasada como argumento incluye algunos elementos que no se encuentran
en la colección, simplemente se ignoran.

• retainAll() hace lo contrario que removeAll (). En lugar de quitar los elementos encontrados
en la colección argumento, retiene todos estos elementos y quita cualquier otro elemento que no se
encuentre en la colección argumento. Hay que tener en cuenta que, si los elementos ya se
encontraban en la colección objetivo, estos son retenidos. Cualquier nuevo elemento encontrado en
la colección argumento que no se encuentre en la colección objetivo no se agrega
automáticamente, simplemente se ignora.

La interfaz Collection tiene dos métodos para revisar si una colección contiene uno o más elementos.

• contains() regresa verdadero si la colección incluye el elemento y falso si no es así.

• containsAll() regresa verdadero si la colección contiene todos los elementos de la colección


argumento y falso si no es así.

• clear() se emplea para vaciar una colección

Se puede revisar el tamaño de una colección utilizando el método size(). El tamaño de una colección
indica el número de elementos que contiene.

Es posible generalizar los tipos y subtipos de Colecciones (Collection) y Mapas (Map) de la siguiente
manera:

Collection<String> x = new HashSet<String>();

Ahora únicamente puede contener instancias u objetos del tipo String. Si se trata de agregar cualquier otra
cosa, o hacer un cast de los elementos en la colección a cualquier otro tipo que no sea String, se arrojará un
error de compilación.

3.3.1 Interface List


List es una colección cuyos elementos permanecen en un orden particular a menos que se modifique la
lista (no significa lista enlazada, aunque es una posible implementación) lo cual quiere decir que se pueden
acceder los elementos en un orden específico o mediante un índice. Puede contener elementos ordenados
y la duplicidad es permitida.

10
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Al ser un subtipo de la interfaz Collection, todos los métodos de ésta también se encuentran disponibles en
la interfaz List. De entre los métodos más comunes en la interfaz List<E> son los siguientes:

• boolean add(T o): Añade un nuevo elemento al final de la colección.

• boolean add(int index, T element): Añade un nuevo elemento en la posición especificada.

• boolean addAll(Collection<? extends E> c): Añade todos los elementos de la colección
especificada a esta colección.

• boolean contains(Object o): Comprueba si el elemento especificado es parte de la colección.

• T get(int index): Recupera el elemento que se encuentra en la posición especificada.

• int indexOf(Object o): Devuelve la primera posición en la que se encuentra el elemento


especificado en la colección, o -1 si no se encuentra.

• int lastIndexOf(Object o): Devuelve la última posición en la que se encuentra el elemento


especificado en la colección, o -1 si no se encuentra.

• T remove(int index): Elimina el elemento de la posición indicada.

• boolean remove(Object o): Elimina la primera ocurrencia del elemento indicado. Si se encontró y se
borró el elemento, devuelve true, en caso contrario, false.

• T set(int index, T element): Reemplaza el elemento que se encuentra en la posición indicada


por el elemento pasado como parámetro. Devuelve el elemento que se encontraba en dicha posición
anteriormente.

• int size(): Devuelve el número de elementos que se encuentran actualmente en la colección.

• void clear():quita todos los elementos de la lista.

Implementaciones de la Interfaz List


Debido a que List es una interfaz, es necesario instanciar una implementación concreta de la interfaz para
poder utilizarla. Existen varias implementaciones de la interfaz List en el API de Java:

• ArrayList: Es una estructura de datos de tipo Array dinámico. A diferencia de los arrays clásicos, un
ArrayList permite aumentar el tamaño del vector indefinidamente (hasta lo que la memoria permita) y
agregar o quitar elementos. A diferencia de LinkedList, ArrayList permite acceder a cualquier elemento
de la lista directamente mediante su índice, lo que la hace especialmente adecuada para búsquedas
rápidas

• LinkedList: Es una lista doble enlazada de Recipientes (nodos) donde cada uno contiene elementos
(objetos, otras listas, etc) y uno o dos punteros hacia posiciones de memoria que apuntan al anterior o
siguiente nodo. Útil cuando se quiere insertar o eliminar elementos al principio o al final de la lista. No
permite acceder a un elemento en concreto de la lista directamente sin recorrer antes los anteriores.

11
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

• Vector: Básicamente tiene las mismas características que un ArrayList, simplemente sus métodos son
sincronizados para la utilización de multihilos seguros.

• Stack: Es una clase de las llamadas de tipo LIFO (Last In - First Out, o último en entrar - primero en
salir). Permite tratar un vector a modo de pila. Las operaciones básicas son push (que introduce un
elemento en la pila), pop (que saca un elemento de la pila), peek (consulta el primer elemento de la
cima de la pila), empty (que comprueba si la pila está vacía) y search (que busca un determinado
elemento dentro de la pila y devuelve su posición dentro de ella).

Las clases Vector y ArrayList se comportan como si se tratara de un arreglo dinámico que crece o decrece
de acuerdo a las necesidades del programa en ejecución. En realidad la única diferencia es que un Vector
puede ser sincronizable, mientras que un ArrayList no, este concepto de sincronización tiene que ver con el
concepto de programación concurrente (hilos de ejecución). Un ArraList es un objeto que se presta mas a
entidades centralizadas donde no es necesario preocuparse por el acceso de varios usuarios.

La Clase ArrayList

Sintaxis:
ArrayList<T>
List<T> lista = new ArrayList<T>()

O en su lugar una declaración mas especifica

ArrayList<T> lista = new ArrayList<T>()

Al utilizar la primera versión, si es necesario en un futuro, podremos migrar fácilmente a una implementación
distinta de List<E> gracias al polimorfismo.

12
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

A continuación, se muestra un par de ejemplos que describen la forma de construir objetos parametrizados:

import [Link];
import [Link];
import [Link];

public class Main


{
public static void main(String[] args)
{
List<Integer> lista = new ArrayList<Integer>();
int val;
Scanner in = new Scanner([Link]);
while(true) {
[Link]("captura un entero (0 para terminar) ");
val = [Link]();
if (val == 0) break;
[Link](val);
}
[Link]("el numero de elementos es " +
[Link]());
for( Integer i : lista ) // iterador
[Link](i);
}
}

El siguiente ejemplo es mas completo y muestra la forma en como podemos emplear fácilmente las
colecciones para manipular datos en un programa. Primero se cuenta con la clase Persona que tiene los
datos relativos a un usuario:

public class Persona


{
private String nombre;
private String apellido;
private short edad;
private char sexo;

public Persona() {}

public Persona(String nombre, String apellido, short edad, char sexo) {


[Link] = nombre;
[Link] = apellido;
[Link] = edad;
[Link] = sexo;
}

public String getApellido() {


return apellido;
}
public void setApellido(String apellido) {
[Link] = apellido;
}
public short getEdad() {
return edad;
}
public void setEdad(short edad) {
[Link] = edad;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
[Link] = nombre;
}
public char getSexo() {
return sexo;
}
public void setSexo(char sexo) {
[Link] = sexo;
}
}

Después se cuenta con una capa de negocios que será la responsable de manipular los datos de la Lista de
Personas:
13
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

import [Link].*;
public class ManejaDatos {
private List<Persona> lista;

public ManejaDatos(){
lista = new ArrayList<Persona>();
}
public void menu(){
short opc = 0;
Scanner in = new Scanner([Link]);
do {
[Link]("1)Altas\n2)Bajas\n3)Cambios\n4)Consultas\n5)Salir\n");
opc = [Link]();
switch (opc) {
case 1 :
[Link]();
break;
case 2 :
[Link]();
break;
case 4 :
[Link]();
break;
case 5 :
return;
}
} while (opc >= 1 && opc <= 5);
return;
}

public void Altas(){


String rpta = "s";
Scanner in = new Scanner([Link]);
do {
[Link](ingresar());
[Link]("deseas otra captura S/N");
rpta = [Link]();
} while ([Link]("S") || [Link]("s"));
return;
}

private Persona ingresar(){


Scanner in;
in = new Scanner([Link]);
[Link]("ingresa los valores ");
[Link]("Nombre ?" );
String nombre = [Link]();
[Link]("Apellido ?" );
String apellido = [Link]();
[Link]("Edad ?" );
short edad = [Link]();
[Link]("Sexo ?" );
char sexo = [Link]().charAt(0);
return new Persona(nombre, apellido, edad, sexo);
}

public void Consultas(){


for(Persona x : lista) {
[Link]("nombre :" + [Link]());
}
}

public void Bajas(){


Scanner in;
in = new Scanner([Link]);
[Link]("Nombre ?" );
String nombre = [Link]();
Persona temp = null;
for ( Persona x : lista )
if ( [Link]().equals(nombre) ) {
temp = x;
break;
}
[Link](temp);
}
}

14
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Finalmente, el programa principal se encarga de arrancar el programa

import [Link];
public class Main
{
public static void main(String[] args) {
ManejaDatos x = new ManejaDatos();
[Link]();
}
}

La Clase LinkedList

Sintaxis:
LinkedList <T> lista = new LinkedList<T>()

Veamos un ejemplo de su implementación:

import [Link]; contenido: [A,A2,F,B,D,C,Z]


import [Link]; contenido: [A,B,D,C,Z]
elimina primero y ultimo: [B,D,C]
inserta A [B,A,D,C]
class LinkedListE { lista tras cambios: [A]
public static void main(String args[])
{
LinkedList<String> lista = new LinkedList<String>();

[Link]("F");
[Link]("B");
[Link]("D");
[Link]("C");
[Link]("Z");
[Link]("A");
[Link](1, "A2");
[Link]("contenido: " + lista);
[Link]("F");
[Link](1);
[Link]("contenido: " + lista);
[Link]();
[Link]();
[Link]("elimina primero y ultimo: " + lista);
if ([Link]("B")) {
[Link]([Link]("B")+1, "A");
[Link]("inserta A " + lista);
}
String val = [Link]();
[Link](val);
[Link](new Predicate<String>() {
public boolean test(String name) {
return [Link]("C") > -1;
}
});
/* expresion lambda para removeIf */
//[Link]( (String name) -> [Link]("C") > -1 );
[Link]("lista tras cambios: " + lista);
}
}

La Clase Stack

Sintaxis:
Stack<T>lista = new Stack<T>()

import [Link];
public class StackE
{
public static void main(String arg[]) {
String cadenano = "no equilibrada (()()()))";
String cadenasi = "equilibrada (())";
[Link]("Verifica paréntesis");
[Link](verificaParentesis(cadenano));
[Link]("Verifica paréntesis");
[Link](verificaParentesis(cadenasi));
}
15
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

public static boolean verificaParentesis(String cadena) {


Stack<String> pila = new Stack<String>();
int i = 0; // Recorremos la expresión por carácter
while (i<[Link]()) {
// Si paréntesis de apertura apilamos
if([Link](i)=='(') [Link]("(");
// Si paréntesis de cierre
else if ([Link](i)==')') {
if (![Link]())
[Link](); // Si pila no vacía des apilamos
else {
// pila no puede empezar con cierre, apilamos y salimos
[Link](")");
break;
}
}
i++;
}
if ( [Link]() )
return true;
else
return false;
}
}

Se puede buscar un objeto dentro del Stack para obtener su índice utilizando el método search(). Éste
llama al método equals() de cada objeto del Stack para determinar si el objeto buscado está presente. El
índice que se obtiene es el índice a partir del tope de la pila, lo cual significa que un elemento con índice 1
se encuentra en el tope.

La Clase Vector
Un vector es similar a un array clásico, la diferencia estriba en que un vector crece automáticamente cuando
alcanza la dimensión inicial máxima. Además, proporciona métodos adicionales para añadir, eliminar
elementos, e insertar elementos entre otros dos existentes. Con Vector, podemos especificar su dimensión
inicial, y cuanto crecerá si rebasamos dicha dimensión.

Vector vector = new Vector(20, 5);

Tenemos un vector con una dimensión inicial de 20 elementos. Si rebasamos dicha dimensión y guardamos
21 elementos la dimensión del vector crece a 25. Al segundo constructor, solamente se le pasa la dimensión
inicial.
Vector vector=new Vector(20);

A continuación, veamos una forma de emplear objetos de esta clase:

import [Link].*; tamano inicial: 0


capacidad inicial: 3
class VectorE { capacidad actual: 5
public static void main(String args[]) { primer elemento: 1
Vector <Number>v = new Vector<Number>(3, 2); ultimo elemento: 7
[Link]("tamano inicial: " + [Link]()); contiene 2
[Link]("capacidad inicial: " + [Link]()); 1 2 5.45 7
[Link](new Integer(1)); elementos en la enumeracion:
[Link](new Integer(2)); 1 2 5.45 7
[Link](new Double(5.45));
[Link](7);
[Link]("capacidad actual: " + [Link]());
[Link]("primer elemento: " + [Link]());
[Link]("ultimo elemento: " + [Link]());
if( [Link]( 2 ) )
[Link]("contiene 2");
for (Number i : v)
[Link](i+" ");

Enumeration vEnum = [Link]();


[Link]("\nelementos en la enumeracion:");
while([Link]())
[Link]([Link]() + " ");
[Link]();
}
}

16
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

También las listas pueden compartir las propiedades de otras interfaces como pueden ser Comparable que
proporciona un orden natural a los elementos, esta característica es muy útil cuando queremos realizar
alguna tarea adicional como por ejemplo el uso de la interfaz estática Collections (no confundir con
Collection) que proporciona algunos algoritmos útiles que ya no tienen que ser re-implementados por el
programador, como por ejemplo un ordenamiento rápido como quickSort o una búsqueda binaria.

A continuación, veremos cómo hacer referencia a estos algoritmos empleando la interfaz Collections, de
modo que para esta interfaz aplicara el mismo algoritmo para los diversos tipos de datos que se requiera
ordenar o buscar de manera que implementa un orden natural (creciente) y la interfaz Comparable re-
implementa el método compareTo en cada uno de los Wrapers existentes (String, Integer, Sort, etc.).

import [Link].*; [abc, acb, bac, bca, cab, cba]


0 1 3 4 5 7 9
public class Sort { el 9 esta en la posicion 6
public static void main(String[] args) { el menor es 0
String [] a = {"cba","cab","bca","bac","acb","abc"}; 9 7 5 4 3 1 0

List<String> list = [Link](a);


[Link](list);
[Link](list);

Integer [] b = {3,4,1,5,7,9,0};
List<Integer> list1 = [Link](b);
[Link](list1);
for (Integer x : list1)
[Link](x + " ");
[Link]("el 9 esta en la posicion "+
[Link](list1,9));
[Link]("el menor es "+
[Link](list1) );
[Link](list1);
for (Integer x : list1)
[Link](x + " ");
}
}

Además, para el caso de los objetos derivados de List no es necesario implementar el método hashCode
de la clase Object, pues no existe un orden de ingreso único en estas estructuras, aunque si puede resultar
útil implementar el método equals, pues permitirá realizar operaciones de búsqueda y ordenamiento más
fácil. En el siguiente ejemplo se muestra el uso de la interface Comparable y la clase estática Collections:

import [Link].*; primera 2 y ultima 4


public class Lista { referencia de Moira
public static void main(String[] args) { Persona :id: 1
//LinkedList<Person> l = new LinkedList<Person>(); nombre: Lalo
// implementa una listas doblemente ligada. Persona :id: 4
ArrayList<Person> l = new ArrayList<Person>(); nombre: Yuyi
// almacea los datos en una tabla hash Persona :id: 3
// agrega los elementos a la lista nombre: Moira
[Link](new Person(1,"Lalo")); Persona :id: 2
[Link](new Person(4,"Yuyi")); nombre: Paula
[Link](new Person(3,"Moira")); Persona :id: 3
[Link](new Person(2,"Paula")); nombre: Moira
[Link](new Person(3,"Moira")); Indice en 1 para Paula
// encuentra primera y ultima referncia para un elemento true
true
int first = [Link](new Person(3,"Moira"));
Valor :id: 1
int last = [Link](new Person(3,"Moira"));
nombre: Lalo
[Link]("primera " + first +" y ultima "
Valor :id: 2
+ last + " referencia de Moira" );
nombre: Paula
// Permite el uos de iteradores para su manejo Valor :id: 3
Iterator<Person> i = [Link](); nombre: Moira
while ([Link]()) { Valor :id: 4
Person p = [Link](); nombre: Yuyi
[Link]("Persona :"+p);
}
/* es posible utilizar la interfaz estatica Collections
* que cuenta con multiples algoritmos */
[Link](l);
int index = [Link](l, new Person(2,"Paula"));
[Link]("Indice en " + index + " para Paula");
[Link]( [Link](new Person(3,"Moira")) );
[Link](new Person(3,"Moira"));

17
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

[Link]( [Link](new Person(3,"Moira")) );


for (Person valor : l){
[Link]("Valor :"+valor);
}
}
}

class Person implements Comparable <Person>{


private int id;
private String nombre;

public Person() {
id = 0;
nombre = "";
}
public Person(int Id, String Nombre){
[Link] = Id;
[Link] = Nombre;
}
public int getId(){
return id;
}
public void setId( int Id) {
[Link] = Id;
}
public String getNombre(){
return nombre;
}
public void setNombre(String Nombre){
[Link] = Nombre;
}
@Override
public String toString(){
return "id: "+ [Link] + "\n"+"nombre: "+[Link];
}
@Override
public boolean equals(Object obj){
if(obj == null) return false;
Person t = (Person)obj;
return obj instanceof Person &&
[Link] == [Link] &&
[Link]([Link]);
}
@Override
// implementado de Comparable, con orden natural
public int compareTo(Person obj){
//return [Link] > [Link] ? -1 :[Link] < [Link] ? 1 :0;
return [Link] - [Link];
}
// clasificando bajo dos criterios
/*
public int compareTo(Person obj) {
int valor = [Link]([Link], [Link]);
if (valor != 0) return valor;
return [Link]([Link]);
}*/
}

3.3.2 Interface Set


Las interfaces Set, SortedSet y NavigableSet representan un conjunto de objetos como en matemáticas, lo
cual significa que cada elemento puede existir solamente una vez en el conjunto, la diferencia entre ellas
radica en que Set es un conjunto de elementos sin orden especifico, SortedSet y NavigableSet contiene
un conjunto de elementos ordenados bajo un orden natural, aunque NavegableSet cuenta con mecanismos
más eficientes para almacenar y remover elementos, además de un conjunto de funciones.

Ya que son un subtipo de Collection, todos los métodos se encuentran disponibles. Por ser interfaces, es
necesario instanciar una implementación concreta de la interfaz para poder usarla:

• Set implementadas en las clases HashSet, LinkedHashSet y TreeSet


• SortedSet implementada en la clase TreeSet
• NavigableSet implementada en la clase TreeSet

18
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Cada una de estas implementaciones se comporta de manera ligeramente distinta respecto al orden de los
elementos, cuando se itera en el conjunto no es posible realizar la eliminación de un elemento (remove).
Para el caso de clases envoltorio (Wrappers) la inserción de elementos repetidos esta asegurada, no así
para los datos compuestos en cuyo caso es indispensable implementar los métodos equals y hasCode
heredados de la clase Object para evitar la duplicidad de elementos en la inserción.

• HashSet es la clase que utilizamos para la interfaz Set que implementa la interface basada en una
tabla hash (una tabla que se construye en base a claves que permiten localizar objetos). En esta
clase el objeto da la posición en la tabla, permitiendo un acceso directo al elemento. Este acceso
directo hace que esta clase sea ideal para búsqueda, inserción y borrado de elementos. No hay
garantía de orden y se permite el uso de elementos nulos.

Ejemplo 1
import [Link].*; Valores de Salida:
public class HashSet1 { Valor :1
public static void main(String[] args) { Valor :3
Set<Integer> s = new HashSet<Integer>(); Valor :5
[Link](3); Valor :8
[Link](1); eliminamos
[Link](8); Valor :1
[Link](5); Valor :5
Valor :7
[Link](3);
Valor :8
for (Integer valor : s)
[Link]("Valor :"+valor);
[Link]("eliminamos");
if ( [Link](3) )
[Link](3);
for (Integer valor : s)
[Link]("Valor :"+valor);
}
}

Ejemplo 2
import [Link].*; class Person {
private int id;
public class HashSet2 { private String nombre;
public static void main(String[] args) { public Person() {}
Set<Person> s = new HashSet<Person>(); public Person(int Id, String Nombre){
[Link](new Person(4,"Lalo")); [Link] = Id;
[Link](new Person(3,"Yuyi")); [Link] = Nombre;
[Link](new Person(2,"Morita")); }
[Link](new Person(1,"Paula")); public int getId(){ return id; }
[Link](new Person(6,"Paula")); public void setId( int Id) { [Link] = Id; }
[Link](new Person(5,"Tita")); public String getNombre(){ return nombre; }
[Link](new Person(2,"Morita")); public void setNombre(String Nombre){
for (Person valor : s) [Link] = Nombre; }
[Link](valor); @Override
[Link]("\ntotal " + [Link]()); public String toString(){
[Link]("eliminamos"); return "("+ [Link] + ","+[Link]+")";
Person t = new Person(6,"Paula"); }
if ( [Link](t) ) @Override
[Link](t); public boolean equals(Object obj){
t = null; Person t = (Person)obj;
for (Person valor : s) { if(obj == null) return false;
if ([Link]() == 3){ return obj instanceof Person &&
t = valor; [Link] == [Link] &&
} [Link]([Link]);
else }
[Link](valor); @Override
} public int hashCode() {
[Link](t); int hash;
[Link]("\ntotal " + [Link]()); hash = [Link];
} hash = hash + ([Link] != null ?
} [Link]() : 0);
return hash;
}
}

Valores de Salida:
(5,Tita)(3,Yuyi)(4,Lalo)(6,Paula)(2,Morita)(1,Paula) total 6
eliminamos
(5,Tita)(4,Lalo)(2,Morita)(1,Paula) total 4

19
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

• LinkedHashSet es la implementación de una tabla hash y una lista enlazada desciende directamente
de un HashSet. Esta implementación difiere con el HashSet en el uso de la lista ligada la cual
garantiza que el orden de los elementos durante la iteración es el mismo orden en el cual fueron
insertados. Reinsertar un elemento que ya se encontraba en el LinkedHashSet no cambia su orden.

Ejemplo 1
import [Link].*; Valores de Salida:
Valor :3
public class LinkedHashSet1 Valor :1
{ Valor :8
public static void main(String[] args) { Valor :5
Set<Integer> s = new LinkedHashSet<Integer>(); Valor :7
[Link](3); eliminamos
[Link](1); Valor :1
Valor :8
[Link](8);
Valor :5
[Link](5);
Valor :7
[Link](7);
[Link](1);
for (Integer valor : s)
[Link]("Value :"+valor);
[Link]("eliminamos");
if ( [Link](3) )
[Link](3);
for (Integer valor : s)
[Link]("Valor :"+valor);
}
}

Ejemplo 2
import [Link].*; class Person {
private int id;
public class HashSet2 { private String nombre;
public static void main(String[] args) { public Person() {}
Set<Person> s = new public Person(int Id, String Nombre){
LinkedHashSet<Person>(); [Link] = Id;
[Link](new Person(4,"Lalo")); [Link] = Nombre;
[Link](new Person(3,"Yuyi")); }
[Link](new Person(2,"Morita")); public int getId(){ return id; }
[Link](new Person(1,"Paula")); public void setId( int Id) { [Link] = Id; }
[Link](new Person(6,"Paula")); public String getNombre(){ return nombre; }
[Link](new Person(5,"Tita")); public void setNombre(String Nombre){
[Link](new Person(2,"Morita")); [Link] = Nombre; }
for (Person valor : s) @Override
[Link](valor); public String toString(){
[Link]("\ntotal " + [Link]()); return "("+ [Link] + ","+[Link]+")";
[Link]("eliminamos"); }
Person t = new Person(6,"Paula"); @Override
if ( [Link](t) ) public boolean equals(Object obj){
[Link](t); Person t = (Person)obj;
t = null; if(obj == null) return false;
for (Person valor : s) { return obj instanceof Person &&
if ([Link]() == 3){ [Link] == [Link] &&
t = valor; [Link]([Link]);
} }
else @Override
[Link](valor); public int hashCode() {
} int hash;
[Link](t); hash = [Link];
[Link]("\ntotal " + [Link]()); hash = hash + ([Link] != null ?
} [Link]() : 0);
} return hash;
}
}

Valores de Salida:
(4,Lalo)(3,Yuyi)(2,Morita)(1,Paula)(6,Paula)(5,Tita) total 6
eliminamos
(4,Lalo)(2,Morita)(1,Paula)(5,Tita) total 4

• TreeSet Esta implementación está basada en el uso de una estructura de árbol (rojo-negro)
permitiendo que los elementos estén ordenados ya sea por orden natural (Comparable), o bien por
orden total definido por un Comparator, haciendo muy rápidas las búsquedas, inserciones y borrados
de sus elementos.

20
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Ejemplo 1
import [Link].*; Valores de Salida:
Valor :1
public class TreeSet1 Valor :3
{ Valor :5
public static void main(String[] args) { Valor :7
NavigableSet<Integer> s = new TreeSet<Integer>(); Valor :8
[Link](3); eliminamos
[Link](1); Valor :1
Valor :5
[Link](8);
Valor :7
[Link](5);
Valor :8
[Link](7); Reversa :[8, 7, 5, 1]
[Link](1);
for (Integer valor : s)
[Link]("Valor :"+valor);
[Link]("eliminamos");
if ( [Link](3) )
[Link](3);
for (Integer valor : s)
[Link]("Valor :"+valor);
NavigableSet<Integer> reversa = [Link]();
[Link]("Reversa :"+reversa);
}
}

Para implementar TreeSet en datos compuestos es necesario implementar los métodos equals y hashCode
de Object, y compareTo de la interface Comparable.

import [Link].*; class Person implements Comparable <Person> {


private int id;
public class TreeSet2 private String nombre;
{ public Person() {}
public static void main(String[] args) { public Person(int Id, String Nombre){
NavigableSet<Person> s = new [Link] = Id;
TreeSet<Person>(); [Link] = Nombre;
[Link](new Person(4,"Lalo")); }
[Link](new Person(3,"Yuyi")); public int getId(){ return id; }
[Link](new Person(2,"Morita")); public void setId( int Id) { [Link] = Id; }
[Link](new Person(1,"Paula")); public String getNombre(){ return nombre; }
[Link](new Person(6,"Paula")); public void setNombre(String Nombre){
[Link](new Person(5,"Tita")); [Link] = Nombre;
[Link](new Person(2,"Morita")); }
for (Person valor : s) @Override
[Link](valor); public String toString(){
[Link]("\ntotal " + [Link]()); return "("+ [Link] + ","+[Link]+")";
[Link]("eliminamos"); }
Person t = new Person(6,"Paula"); @Override
if ( [Link](t) ) public boolean equals(Object obj){
[Link](t); Person t = (Person)obj;
t = null; if(obj == null) return false;
for (Person valor : s) { return obj instanceof Person &&
if ([Link]() == 3){ [Link] == [Link] &&
t = valor; [Link]([Link]);
} }
else @Override
[Link](valor); public int hashCode() {
} int hash;
[Link](t); hash = [Link];
[Link]("\ntotal " + [Link]()); hash = hash + ([Link] != null ?
[Link]([Link]()); [Link]() : 0);
} return hash;
} }
// implementa Comparable, con orden natural
public int compareTo(Person obj){
return [Link] - [Link];
}
}

Para el caso de la interface NavegableSet existe varios métodos como descendingSet que es capaz de
invertir el orden de los elementos realizando un recorrido e postfijo del árbol binario implementado. También
los metodos: higher, ceiling, floor, pollFirst, pollLast, entre otros, que nos permiten hacer accesos y
eliminacion de los objetos insertados dentro del árbol. Además, se trata de una implementación no
sincronizada.

21
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

Valores de Salida:
(1,Paula)(2,Morita)(3,Yuyi)(4,Lalo)(5,Tita)(6,Paula) total 6
eliminamos
(1,Paula)(2,Morita)(4,Lalo)(5,Tita) total 4
[(5,Tita), (4,Lalo), (2,Morita), (1,Paula)]

3.3.3 Interface Queue


La interfaz Queue (cola) es un subtipo de la interfaz Collection, representa una lista ordenada de objetos
justo como List pero su uso es ligeramente distinto. Una cola está diseñada para tener sus elementos
insertados al final de la cola y removidos del inicio. Justo como una fila de banco o de supermercado.

Al ser un subtipo de Collection, todos los métodos de Collection también se encuentran disponibles. Como
Queue es una interfaz, es necesario instanciar una implementación concreta para poder utilizarla. Existen
principalmente 3 implementaciones concretas para ésta interfaz:

• LinkedList es una implementación estándar de una cola.


• PriorityQueue guarda sus elementos internamente de acuerdo a su orden natural (interfaz
Comparable) o mediante un orden preferente (interfaz Comparator).
• ArrayDeque lista circular que implementan adicionalmente las operaciones de una pila doble-
ended-queue

Veamos el siguiente ejemplo implementando una lista ligada como cola con la clase LinkedList:

import [Link].*;

public class Temp {


public static void main(String[] args) {
Queue<String> qe=new LinkedList<String>();
// agrega elementos a la cola
[Link]("b"); // permite manejo de excepciones
[Link]("a");
[Link]("c");
[Link]("e");
[Link]("d");
// determina el tamaño
[Link]("tamaño inicial :"+[Link]());
// recorreo la cola
for (String valor: qe)
[Link]("valores :"+valor);

// mostrar el valor del frente


[Link]("frente :"+[Link]());
// permite manejo de excepciones
[Link]("frente :"+[Link]());

// obtiene el primero y lo remueve


[Link]("extrae :"+[Link]());
// permite manejo de excepciones
[Link]("extrae :"+[Link]());

[Link]("f"); // inserta elementos


[Link]("valores en la cola");
Iterator <String> it = [Link]();
while ([Link]()){
String t = [Link]();
[Link]("valores :"+t);
}
[Link]("tamaño final :"+[Link]());
[Link]("contiene f " + [Link]("f"));
[Link]("es vacia " + [Link]());
}
}

22
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Veamos el siguiente ejemplo implementando una cola con prioridad con la clase PriorityQueue:

import [Link].*;

public class PrimeroPares


{
public static void main(String[] args) {
PriorityQueue<Integer> pQ = new PriorityQueue<Integer>( 1, new Comparator<Integer>()
{
public int compare(Integer val1, Integer val2) {
boolean r1 = esPar(val1);
boolean r2 = esPar(val2);
if (r1 == r2){
return [Link](val2); // comparacion natural
} else if (r1) {
return -1;
} else if(r2) {
return 1;
}
return 0;
}
});

[Link](10);
[Link](8);
[Link](6);
[Link](4);
[Link](2);
[Link](9);
[Link](7);
[Link](5);
[Link](3);
[Link](1);

while(true) {
Integer frente = [Link]();
if(frente == null) {
break;
}

[Link](frente + " ");


}
}

public static boolean esPar(int n) {


return (n % 2 != 0) ? false : true;
}
}

Veamos el siguiente ejemplo implementando una cola con prioridad con la clase PriorityQueue e
implementando la interfaz Comparable, en el ejemplo se trata de simular el acceso a la sala de urgencias de
un hospital, dando prioridad a ciertos pacientes.

import [Link];

public class Paciente implements Comparable <Paciente>{


private int id;
private String nombre;
private boolean prioridad;

public Paciente(int id, String nombre, boolean prioridad) {


[Link] = id;
[Link] = nombre;
[Link] = prioridad;
}
public int getId() {
return id;
}
public void setId(int id) {
[Link] = id;
}
public String getNombre() {
return nombre;

23
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

}
public void setNombre(String nombre) {
[Link] = nombre;
}
public boolean esPrioridad() {
return prioridad;
}
public void setPrioridad(boolean prioridad) {
[Link] = prioridad;
}
@Override
public boolean equals(Object obj){
if(obj == null) return false;
Paciente t = (Paciente)obj;
return obj instanceof Paciente &&
[Link] == [Link] &&
[Link]([Link]) &&
[Link] == [Link];
}
@Override
public int compareTo(Paciente p){
return ([Link]() == [Link]()) ? // Si es la misma prioridad
// compara en base a su Id
([Link]([Link]()).compareTo([Link]()))
// en caso contrario, si p tiene mas prioridad
: ([Link]() ? 1 : -1);
}
}

import [Link].*;

public class Pqueue {


public static void main(String[] args) {
Queue<Paciente> listaPacientes = new PriorityQueue<Paciente>();

[Link](new Paciente(4, "Paciente 4", false));


[Link](new Paciente(2, "Paciente 2", false));
[Link](new Paciente(3, "Paciente 3", true));
[Link](new Paciente(1, "Paciente 1", false));
[Link](new Paciente(5, "Paciente 5", true));

[Link]("lista de pacierntes");
while(true) {
Paciente currentPatient = [Link]();
if(currentPatient == null) {
break;
}

[Link]([Link]() + " <- ");


}
[Link]();
}
}

Existe también el caso de las listas circulares que se comportan como un arreglo de dimensión variable
empleado para realizar adicionalmente las operaciones de una pila como es el caso de eliminar por detrás
(removeLast) e insertar por el frente (addFirst), este tipo de estructuras son conocidas como Doble-Ended-
Queue (deque), es el caso de la clase ArrayDeque:

import [Link].*;
public class ArrayDeque1
{
public static void main(String[] args)
{
Deque<Integer> d = new ArrayDeque<Integer>(10);

[Link](2);
[Link](4);
[Link](6);
[Link](2);
for (Integer element : d)
[Link]("Elemento: " + element);
// addFirst() inserta al inicio
[Link](10);
// addLast() inserta al final
[Link](14);

24
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

[Link]("tamaño del deque : " + [Link]());


[Link]("Elementos : " + d);
[Link]("contiene a 10 : " + [Link](10));
// descendingIterator() : orden inverso del deque
[Link]("Elementos orden inverso :");
for(Iterator dItr = [Link](); [Link]();)
[Link]([Link]() + " ");
// element() metodo
[Link]("Elemento en la cabeza: " + [Link]());
[Link]("getFirst(): " + [Link]());
[Link]("getLast(): " + [Link]());
[Link]("empty : " + [Link]());
//toArray() metodo :
Object[] arr = [Link]();
[Link]("tamaño : " + [Link]);
[Link]("elementos del Array: ");
for(int i=0; i<[Link] ; i++)
[Link](" " + arr[i]);
[Link]("metodo clear() ");
[Link]();
[Link]("empty : " + [Link]());
}
}

3.3.4 Interface Map


Las Interfaces Map y SortedMap son colecciones cuyos elementos se encuentran organizados por una
relación de campo llave e información (llave, información). Las siguientes implementaciones concretas para
estas interfaces son:

• Map emplea las clases HashMap y LinkedHashMap


• SortedMap emplea la clase TreeMap

La interfaz Map, representa un objeto que como ya se menciono sirve para ligar un campo clave con un
valor u objeto. De manera que es posible pensar que un mapa es una especie de diccionario de datos
donde se realiza dicha asociación de datos.

Como ejemplo podríamos pensar en que una clave puede ser un número de pasaporte de una persona y
como valor el objeto Persona que contiene toda la información y métodos de una persona en concreto.
Aunque frecuente es que sea algo conciso (como un número) también puede ser otras cosas. Por ejemplo,
el objeto clave podría ser una imagen, al que le correspondiera otro objeto.

Un mapa esta abierto a cualquier tipo de objeto, es importante resaltar que un mapa no puede tener claves
duplicadas, ya que la clave es un identificador de objetos único. Por ejemplo, no deberían existir dos claves
exactamente iguales de pasaporte o de registro federal de contribuyente.

Un mapa puede ser visto como un conjunto de claves, colecciones de valores, o un conjunto de pares
compuesto por clave y valor mapeado. De manera que hay que tener especial cuidado con los métodos
equals y hashCode ya que estos en general no están bien definidos o no resultan útiles sobre un Map
genérico o recién creado, por lo que hay que sobre-escribir estos métodos para que tengan un
funcionamiento acorde a nuestras necesidades.

Por otro lado, la interfaz SortedMap permite que los elementos dentro del conjunto estén ordenados
totalmente bajo un cierto criterio o campo llave, agilizando la manipulación de los datos. Para que la
ordenación de las claves se de, es necesario que los elementos pertenezcan a una clase que implemente la
interfaz Comparable o tener un Comparator adecuado, por ejemplo, la clase Integer ya cuenta con un
orden natural valido, mientras que en nuestras nuevas implementaciones se tendrá que definir.

Las instrucciones mas usadas en una estructura de Diccionario (Map) son los siguientes:

• int size(): Devuelve el numero de elementos del Map

• boolean isEmpty(): Devuelve true si no hay elementos en el Map y false si si los hay.

• V put(K clave, V valor): Añade un elemento al Map

25
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez

• V get(K clave): Devuelve el valor de la clave que se le pasa como parámetro o null si la clave no
existe.

• void clear(): Borra todos los elementos del Map

• V remove(K clave): Borra el par clave/valor de la clave que se le pasa como parámetro.

• boolean containsKey(): Devuelve true si en el Map hay una clave que coincide con K

• boolean containsValue (V valor): Devuelve true si en el Map hay un Valor que coincide con V.

• T values (): Devuelve una Collection con los valores del Map.

HashMap: Los elementos que inserta en el map no tendrán un orden específico. No aceptan claves
duplicadas ni valores nulos.

LinkedHashMap: Inserta en el Map los elementos en el orden en el que se van insertando; es decir, que no
tiene una ordenación de los elementos como tal, por lo que esta clase realiza las búsquedas de los
elementos de forma más lenta que las demás clases.

TreeMap: El Mapa lo ordena de forma "natural". Por ejemplo, si la clave son valores enteros (como luego
veremos), los ordena de menor a mayor. Con un objeto SortedMap, los elementos serán insertados dentro
de una estructura de árbol, bajo el algoritmo rojo-negro:

Podemos incorporar elementos dentro del mapa, empleando elementos simples:

import [Link].*;

class Mapa1 {
public static void main(String ... args){
//Map<Integer, String> map = new HashMap<Integer, String>();
Map<Integer, String> map = new LinkedHashMap<Integer, String>();
//SortedMap<Integer, String> map = new TreeMap<Integer, String>();
[Link](1, "Pablo");
[Link](15, "Julia");
[Link](3, "Ramon");
[Link](5, "Horacio");
[Link](11, "Ximena");
[Link](14, "Alonso");
Iterator it = [Link]().iterator();
while([Link]()){
Integer key = (Integer)[Link]();
[Link]("Clave: " + key + " -> Valor: " + [Link](key));
}
[Link]("[Link]() = " + [Link]());
[Link]("[Link]() = " + [Link]());
[Link]("Obtener elemento 5 = " + [Link](5));
[Link]("Borrar elemento 15 " + [Link](15));
[Link]("Que pasa si obtenemos la clave 15 " + [Link](15));
[Link]("Existe un elemento con la clave 15 " + [Link](15));
[Link]("Existe un elemento con la clave 1 " + [Link](1));
[Link]("Existe el valor 'Ximena' " + [Link]("Ximena"));
[Link]("Existe el valor 'Ricardo' " + [Link]("Ricardo"));
[Link]("Borramos todos los elementos ");
[Link]("valores " + [Link]());
[Link]();
[Link]("Tamaño " + [Link]());
[Link]("Esta vacio " + [Link]());
[Link](15, "Cleta");
// regresa una vista de un conjunto de llaves contenidas en el mapa
for ( Integer key : [Link]() )
[Link]("Clave: " + key + " -> Valor: " + [Link](key) );
[Link](1, "Pancho");
// regresa un conjunto de mapeos contenidos en el mapa
for ( [Link]<Integer,String> t : [Link]() )
[Link]("Clave: " + [Link]() + " -> Valor: " + [Link]() );

}
}

26
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez

Para el caso de elementos combinados como es el caso de las clases compuestas podemos emplear un
mapa, de manera que un elemento de tipo String puede servir como clave de acceso mientras se asocia a
un objeto.

public class Persona {


public int id;
public String nombre;
public int edad;

public Persona(int id, String nombre, int edad) {


[Link] = id;
[Link] = nombre;
[Link] = edad;
}

public String getNombre(){


return nombre;
}

public String toString() {


return "ID: "+id+" Nombre: "+nombre+" Edad: "+edad+"\n";
}

@Override
public boolean equals(Object obj){
Persona t = (Persona)obj;
if(obj == null) return false;
return obj instanceof Person &&
[Link] == [Link] &&
[Link]([Link]) &&
[Link] == [Link];
}
@Override
public int hashCode() {
int hash;
hash = [Link];
hash = hash + ([Link] != null ? [Link]() : 0);
hash = hash + [Link];
return hash;
}
}

import [Link].*;

public class MapaHash {


public static void main (String []args) {
SortedMap<String,Persona> mapa = new TreeMap<String,Persona>();
//Map<String,Persona> mapa = new HashMap<String,Persona>();

[Link]("Martha", new Persona(1,"Martha",37));


Persona p = new Persona(2,"Paula",3);
[Link]("Paula",p);
p = new Persona(3,"Moira",7);
[Link]("Moira",p);
p = new Persona(4,"Ana",7);
[Link]("Ana",p);
[Link]("Martha", new Persona(5,"Martha",37));
[Link]("Personas en el mapa: \n"+mapa);

[Link]("Martha", new Persona(1,"Martha",38));

for ( [Link]<String,Persona> t : [Link]() ) {


[Link]("Clave: " + [Link]() + " -> Valor: " + [Link]() );
if ([Link]().getNombre().equals("Moira"))
[Link]("la clave es " + [Link]());
}

//p = new Persona(4,"Ana",7);


[Link]("Contiene "+[Link](p));
[Link]("Contiene "+[Link]("Ana"));
p = [Link]("Ana");
[Link](p);
}
}

27

También podría gustarte