Introducción a la Programación Genérica en Java
Introducción a la Programación Genérica en Java
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).
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:
public Generico1() {
setValor(null);
}
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:
public Generico2() {
this(null, null);
}
2
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez
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:
public Generico3() {
setValor(null);
}
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:
public Generico4() {
this(null);
}
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:
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.
public Comodin() {
this(null);
}
4
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez
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.
public Comodin() {
this(null);
}
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
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.
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
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:
PilaG(int tam){
[Link] = new G [tam];
[Link]=0;
}
public G pop(){
if(pivote <= 0)
return null;
return elementos[--pivote];
}
public G nextPop(){
if(pivote <= 0)
return null;
return elementos[pivote-1];
}
Los arreglos parametrizados deberán ser modificados con un cast para que puedan funcionar, como se
presentan a continuación:
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 T getDatoCima() {
return [Link];
}
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:
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.
class FuncionGenerica
{
public static <T extends Comparable<T>> T minimo( T a, T b) {
if( [Link](b) < 0 )
return a;
else
return b;
}
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
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.
Iterator<T> it = [Link]();
while ([Link]()) {
T e = [Link]();
[Link]("Objeto:"+e);
}
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.
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:
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.
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 addAll(Collection<? extends E> c): Añade todos los elementos de la colección
especificada a esta colección.
• 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.
• 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>()
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];
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 Persona() {}
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;
}
14
UAA – Sistemas Electrónicos Programación III Eduardo Serna-Pérez
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>()
[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
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.
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);
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.).
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:
17
UAA-DSE Programación 3 / Java Eduardo Serna-Pérez
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]);
}*/
}
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:
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.
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)]
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:
Veamos el siguiente ejemplo implementando una lista ligada como cola con la clase LinkedList:
import [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].*;
[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;
}
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];
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].*;
[Link]("lista de pacierntes");
while(true) {
Paciente currentPatient = [Link]();
if(currentPatient == null) {
break;
}
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
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:
• boolean isEmpty(): Devuelve true si no hay elementos en el Map y false si si los hay.
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.
• 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:
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.
@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].*;
27