Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Tema 9. Colecciones II
1. El tipo Map
1.1 Definición
El tipo de dato Map, incluido en el paquete java.util, permite modelar el concepto de aplicación: una
relación entre los elementos de dos conjuntos de modo que a cada elemento del conjunto inicial le
corresponde uno y solo un elemento del conjunto final. Los elementos del conjunto inicial se denominan
claves (keys) y los del conjunto final valores (values).
En la figura 1 puede verse un ejemplo de Map. En este caso las claves son cadenas de caracteres, cada una
de las cuales representa un valor numérico en diversas representaciones textuales, y los valores son enteros.
Cada cadena tiene asociada el valor numérico correspondiente.
2 Fundamentos de Programación
“uno”
“1” 1
“I”
“1.0”
“2”
2
“II”
“1+1”
Este mismo ejemplo se podría representar como una tabla con dos columnas:
Clave Valor
“uno” 1
“1” 1
“I” 1
“1.0” 1
“2” 2
“II” 2
“1 + 1” 2
Como entre las claves no puede haber elementos duplicados (una clave no puede estar asociada con más de
un valor), las claves forman un conjunto (Set). Sin embargo sí que puede haber valores duplicados, por los
que estos están en una Collection.
Por tanto, un Map está definido por un conjunto de claves, una colección de valores y un conjunto de pares
clave-valor (también llamadas entradas), que son realmente los elementos de los que está compuesto.
Dentro de la jerarquía de tipos de Java, Map no extiende a Collection ni a Iterable. Por tanto, no se puede
aplicar un for extendido sobre sus elementos, es decir, sobre sus pares, salvo que accedamos explícitamente
a ellos. Los Map se utilizan en muchas situaciones. Por ejemplo, los listines telefónicos (clave: nombre del
contacto, valor: número de teléfono), listas de clase (clave: nombre del alumno, valor: calificaciones), ventas
de un producto (clave: código de producto, valor: total de euros de recaudación), índices de palabras por
páginas en un libro (clave: palabra, valor: lista de números de página donde aparece), la red de un Metro
(clave: nombre de la estación, valor: conjunto de líneas que pasan por esa estación, o al revés, clave: número
de línea, valor: lista de estaciones de esa línea), etc. Tiene también muchas otras aplicaciones en
9. Colecciones II 3
Centrándonos en Java, la interfaz Map tiene dos tipos genéricos, que suelen denominarse K (de Key) y V (de
Value): Map<K, V>. El Map del ejemplo anterior sería Map<String, Integer>.
La clase que implementa la interfaz Map es HashMap (aunque también se puede utilizar TreeMap, que se
verá más adelante). La clase HashMap obliga a definir de forma correcta el método hashCode del tipo de las
claves para evitar comportamientos incorrectos, como claves repetidas. Asimismo, al igual que pasa en el
tipo Set, los objetos mutables que se introducen en un Map son “vistas” o referencias a objetos y, por tanto,
si estos cambian, el Map puede dejar de ser coherente, esto es, podría tener claves repetidas.
Un Map cuyas claves sean String y cuyos valores sean Integer se definiría e inicializaría de la siguiente forma:
void clear()
Elimina todos los elementos (pares o entradas) del Map.
V get(Object key)
Devuelve el valor asociado con la clave especificada o null si esa clave no está
asociada con ningún valor (la clave no está en el conjunto de claves).
boolean isEmpty()
Devuelve true si el Map no contiene ningún par.
Set<K> keySet()
Devuelve un Set que es una vista de las claves que contiene el Map.
1
http://docs.oracle.com/javase/7/docs/api/java/util/Map.html
4 Fundamentos de Programación
V remove(Object key)
Elimina el par que tiene como clave el parámetro especificado. Devuelve el valor
previamente asociado con la clave o null, si la clave no existía.
int size()
Devuelve el número de pares del Map.
Collection<V> values()
Devuelve una Collection que es una vista de los valores del Map.
Set<Map.Entry<K,V>> entrySet()
Devuelve una vista del conjunto de todos los pares del Map (el tipo Map.Entry se verá
más adelante).
Tanto los conjuntos devueltos por keySet y entrySet como la colección devuelta por va,lues son vistas del
Map original, por lo que las modificaciones que se realicen sobre estos repercuten en los pares almacenados
en el Map original (con las posibles repercusiones negativas) y viceversa. Hay que destacar que esos tres
objetos son iterables. El orden en el que el iterador recorre los elementos de los conjuntos (keySet y
entrySet) o la colección (values) es impredecible.
La clase HashMap tiene dos constructores: el constructor sin argumentos, que construye un Map vacío, y el
constructor con un argumento de tipo Map (constructor copia), que construye un Map con los mismos pares
que el Map que se le pasa como argumento (equivale a crear un Map vacío y aplicar putAll). Existe una clase
LinkedHashMap, que se comporta igual que HashMap excepto en que los iteradores sobre las claves, los
valores o los pares los devuelven en el orden en que se insertaron.
K getKey()
Devuelve la clave del par.
V getValue()
Devuelve el valor del par.
V setValue(V value)
Modifica el valor del par, dándole como nuevo valor value. Devuelve el valor (segunda componente)
previo del par.
2
Más información en: http://docs.oracle.com/javase/7/docs/api/java/util/Map.Entry.html
9. Colecciones II 5
Por ejemplo, si tenemos una entrada (par) p que asocie “II” con 2, p.getKey() devuelve la cadena “II” y
p.getValue() devuelve el entero 2; si se escribe p.setValue(3), devuelve el entero 2 y modifica el valor
asociado con la clave dándole el valor 3. El toString de las entradas es de la forma clave=valor, de modo que
la representación como cadena del par resultante sería II=3.
En el paso a. se construye el objeto tipo Map invocando al constructor de la clase HashMap. El paso b.
normalmente incluye un recorrido sobre el conjunto de claves. Para cada clave se calculará el valor
correspondiente, normalmente mediante algún tipo de cálculo u operación de acceso a una colección de
datos a partir de la clave. Una vez tenemos el par clave-valor a insertar en el Map, nos preguntamos en el
paso c. mediante el método containsKey si la clave ya estaba anteriormente en el Map.
Si la clave ya estaba, debemos obtener el valor asociado a esa clave en el Map mediante el método get.
Dependiendo de si la actualización consiste en sustituir un nuevo valor por el anterior o actualizar el ya
existente, se ejecutan los pasos e. y/o f. Por ejemplo, si el Map asocia a cada palabra una lista con los
números de las páginas de un libro donde aparece esa palabra, la actualización será añadir a la vista de la
lista que conforma el valor un nuevo elemento y, por tanto, no hace falta invocar al método put (ya que List
es un tipo mutable, y podemos hacerle modificaciones). Sin embargo, si el Map es una asociación entre el
código de un producto y sus ventas, para actualizar las ventas, al invocar al método get obtendríamos el
valor de las ventas pasadas, y a este dato se le deberían sumar las nuevas ventas. Como Double es un tipo
inmutable, la operación suma devolverá un objeto nuevo y, por tanto, deberíamos actualizar, mediante el
método put, la asociación de ese nuevo objeto de tipo Double con las ventas realizadas, y el código del
producto como clave.
Si la clave no estaba en el Map, se calcula el valor inicial en el paso h, para añadir el par clave-valor mediante
el método put en el paso i.
Ejemplo 1
Supongamos que se quiere calcular la frecuencia absoluta de aparición o número de veces que aparecen los
caracteres en un String. Para ello se define un Map cuyas claves son de tipo Character y cuyos valores son de
6 Fundamentos de Programación
tipo Integer. El método recibirá un String y devolverá un objeto de tipo Map<Character, Integer>. El código
del método sería:
Nótese como el método sigue fielmente el esquema antes indicado. Se recorren los caracteres del String de
entrada mediante un for clásico. Para cada carácter se pregunta si ya está en el Map, de forma que si está, se
obtiene el valor correspondiente al número de veces que ha aparecido anteriormente, se incrementa en uno
y se vuelve a actualizar la asociación entre el carácter y el nuevo valor (un nuevo objeto, puesto que Integer
es un tipo inmutable). Al ser Integer un tipo inmutable, la llamada al método put es obligatoria. En el caso de
que el carácter no estuviera en el Map, se inicializa la frecuencia a 1.
Ejemplo 2
Supongamos que tenemos una lista de String que denominamos palabras y se quiere obtener un índice de
las posiciones que ocupan las cadenas de la lista palabras mediante una lista de enteros. El argumento de
entrada será por tanto de tipo List<String> y el argumento de salida un Map<String, List<Integer>>. Por
ejemplo, para una lista que contuviera las palabras de la cadena “la palabra que más aparece en este texto
es la palabra palabra”, la salida sería
{más=[3], texto=[7], aparece=[4], palabra=[1, 10, 11], la=[0, 9], en=[5], que=[2],
este=[6], es=[8]}
Indicando que ‘la’ está en las posiciones 0 y 9, ‘palabra’ en la 1, 10 y 11, etc. El método tendría el siguiente
código:
indice.put(palabra, lis);
}
pos++;
}
return indice;
}
Nótese que la principal diferencia con el ejercicio anterior es que no es necesario invocar al método put en el
caso de que la palabra ya esté en el índice, puesto que List es un tipo mutable. Observe que la sentencia
indice.get(palabra).add(pos);
es equivalente a
Igualmente, debe observar que incluso en este caso, tampoco es necesario invocar a put ya que lis es una
vista de la lista correspondiente y por tanto al añadir un elemento a lis, ya lo estamos haciendo a la lista
guardada en el conjunto imagen del Map.
Este problema se puede restringir a que aparezcan sólo las páginas en las que aparecen las palabras
importantes de un texto: las que se denominan palabras clave. (Por ejemplo, en este tema nos interesaría
saber en qué páginas aparecen las palabras “Map”, “HashMap”, “TreeMap”, “Guava”, etc., pero no en cuáles
aparece “en”, “la”, “las”, etc.). Supongamos que tenemos las palabras claves en un Set de String, ¿cómo
modificaría el código del método anterior para que en el Map sólo estuvieran las palabras clave?
2. El tipo SortedMap
2.1 Definición
El tipo SortedMap es un subtipo de Map en el que el conjunto de las claves está ordenado. La clase que
implementa SortedMap es TreeMap. Es necesario que el tipo de las claves tenga un orden natural (es decir,
que implemente Comparable) o que se proporcione un orden alternativo mediante un Comparator. Por
tanto, una inicialización de SortedMap como la siguiente:
Crea un SortedMap donde las claves son de tipo String y están ordenadas por el orden natural. Para crear un
SortedMap por un orden alternativo debemos invocar al constructor pasándole como argumento un
comparador:
Las reflexiones que se hicieron para el tipo SortedSet respecto del orden definido por un Comparator siguen
siendo válidas para SortedMap. Esto es, dos claves c1 y c2 son iguales para un SortedMap si
compare(c1,c2) == 0. Por tanto, si el Comparator no rompe los empates por el orden natural, en el
SortedMap anterior sólo habría un Libro clave por cada número de páginas.
2.2 Métodos
SortedMap hereda de Map. Por tanto, todos los métodos de Map son válidos también para un objeto
SortedMap. Además, proporciona algunos métodos para manejar el orden en el conjunto de las claves como
podemos ver a continuación3:
K firstKey()
Devuelve la primera clave del Map, según el orden que tenga inducido.
K lastKey()
Devuelve la última clave del Map.
Los métodos keySet, entrySet y values tienen el mismo perfil que en Map, esto es, devuelven Set, Set y
Collection, respectivamente. Los iteradores sobre ellos recorren los elementos en el orden en el que están
ordenadas las claves.
3
http://docs.oracle.com/javase/7/docs/api/java/util/SortedMap.html
9. Colecciones II 9
3 La librería Guava
Guava es una librería de programación de código abierto diseñada por ingenieros de Google que amplía la
funcionalidad de la API de Java en muchos aspectos. Incluye nuevos tipos de datos, factorías y clases de
utilidad para los tipos existentes y para los nuevos, y una amplia funcionalidad para manejar objetos
iterables. Se usará extensamente de aquí en adelante. En este tema nos vamos a centrar en los aspectos
relacionados con las Colecciones vistas hasta el momento y a introducir algunas nuevas.
La página principal desde la que puede llegarse a la documentación, código fuente, etc. es
https://code.google.com/p/guava-libraries/. En ella podemos encontrar un enlace para la descarga de la
librería (guava-14.0.jar). La versión actual es la 14.0, liberada el 25 de febrero de 2013. El procedimiento
para la instalación es el siguiente:
1. Descargamos la librería.
2. Podemos crear una copia de la librería en cada proyecto que desarrollemos o bien guardar una sola
copia y referenciarla desde todos los proyectos. Vamos a continuar con la descripción de esta
modalidad, que nos parece más operativa.
3. Movemos el archivo a una carpeta donde la tengamos bien localizada; por ejemplo, podemos guardarla
en C:/Archivos de programas/eclipse
4. En cada proyecto donde queramos utilizar Guava, pulsamos con el botón derecho del ratón sobre el
proyecto. En el menú contextual que se abre hacemos clic en “Build Path” y en el menú que se despliega
en “Add External Archives”. Navegamos por el sistema de archivos hasta encontrar la librería, la
marcamos, le damos a abrir y listo. A partir de este momento disponemos de toda la funcionalidad de la
librería.
10 Fundamentos de Programación
Como hemos dicho, Guava proporciona clases factoría para la creación de estos tipos de datos. Estas clases,
además de los métodos propios de las factorías, tienen una serie de métodos de utilidad. Es decir, son a la
vez factorías y clases de utilidad.
Lists ofrece más formas de crear listas que las que tienen los constructores de ArrayList y LinkedList, que
recordemos que son:
El constructor sin argumentos, que crea una lista vacía.
El constructor con un argumento de tipo Collection, que crea una lista con los mismos elementos
que la colección que se le pasa (constructor copia).
Para las ArrayList (hay algunos otros con aspectos técnicos, pero vamos a enumerar los más útiles):
static <E> ArrayList<E> newArrayList()
4
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Lists.html
9. Colecciones II 11
El primer método equivale al constructor vacío, el segundo permite crear una lista a partir de un número
variable de objetos separados por comas, el tercero equivale al constructor copia (recordemos que
Collection es subtipo de Iterable) y el último permite crear una lista a partir de un Iterator. Usando el
segundo método se puede crear una lista, haciendo por ejemplo:
Obsérvese que en el método no se indica el tipo (ni en ningún otro método creacional): éste se toma del tipo
del que esté declarada la variable; en este caso List<String>.
La clase Lists tiene algunos métodos de utilidad adicionales, como reverse, que dada una lista devuelve una
vista de la lista original con los elementos invertidos de orden, o charactersOf, que dada una cadena de
caracteres (String) devuelve una lista de caracteres con los caracteres de los que está compuesta la cadena.
5
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html
12 Fundamentos de Programación
elements in order.
La clase Sets tiene otros métodos que permiten, por ejemplo, calcular el producto cartesiano de varios
conjuntos o listas, calcular la unión, la intersección, la diferencia o la diferencia simétrica de dos conjuntos o
el conjunto de las partes de un conjunto.
static newIdentityHashMap()
<K,V> IdentityHashMap<K,V>
Creates an IdentityHashMap instance.
static newLinkedHashMap()
<K,V> LinkedHashMap<K,V>
Creates a mutable, empty, insertion-
ordered LinkedHashMap instance.
6
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Maps.html
9. Colecciones II 13
5. El tipo Multiset
El tipo Multiset7 (atención a la ese minúscula), en español multiconjunto, es un conjunto en el que cada
elemento tiene asociada una multiplicidad que indica cuántas veces aparece el elemento. Sirve, por ejemplo,
para contar con facilidad frecuencias. Sólo aparece en Guava: no hay ninguna interfaz equivalente en la API
de Java.
El tipo tiene un tratamiento distinto a los anteriores en cuanto a su creación: se crean objetos de este tipo
mediante los métodos create de la clase factoría HashMultiset8. Tiene dos métodos creacionales: uno sin
argumentos, que crea un Multiset vacío; y otro con un argumento de tipo Iterable, que crea un Multiset con
los mismos elementos que el iterable:
produce la salida:
[P, A x 3, N x 2]
7
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multiset.html
8
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/HashMultiset.html
14 Fundamentos de Programación
esto es, “P” aparece una vez, “A” tres y “N” dos (al igual que en los conjuntos, el orden de aparición es
impredecible: depende del hashCode de los elementos, entre otros factores).
Existe una clase LinkedHashMultiset9 donde los elementos aparecen en el orden en el que se insertaron
cuando se itera sobre ellos.
Returns true if this multiset contains at least one occurrence of each element in
the specified collection.
Returns the number of occurrences of an element in this multiset (the count of the
element).
Set<E> elementSet()
Set<Multiset.Entry<E>> entrySet()
Iterator<E> iterator()
Removes a single occurrence of the specified element from this multiset, if present.
boolean removeAll(Collection<?> c)
boolean retainAll(Collection<?> c)
9
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/LinkedHashMultiset.html
9. Colecciones II 15
Adds or removes the necessary occurrences of an element such that the element
attains the desired count.
Como todos los tipos, naturalmente también tiene los métodos equals, hashCode y toString, aunque no los
mencionemos.
La clase Multisets10 es una clase de utilidad que permite hacer operaciones sofisticadas sobre los
multiconjuntos. Destacamos los siguientes métodos:
10
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html
16 Fundamentos de Programación
Existe también el tipo SortedMultiset11, en el que los elementos se ordenan según el orden natural o el
inducido por un Comparator. La clase TreeMultiset12 tiene tres métodos create (equivalentes a los
constructores de TreeSet) que crean multiconjuntos ordenados: uno sin argumentos, que crea un Multiset
vacío en el que los elementos se ordenan por el orden natural de los elementos; uno con un argumento de
tipo Iterable, que contendrá los mismos elementos que el argumento pero con los elementos ordenados por
su orden natural; y otro con un argumento de tipo Comparator, que crea un Multiset vacío en el que los
elementos se ordenan según el criterio aportado por el comparador.
produce la salida:
6. El tipo Multimap
El tipo Multimap13 (cuidado con la segunda eme minúscula) es como un Map pero en el que si se asigna más
de un valor a la misma clave (se hacen varios put con el mismo valor de clave), se mantienen todos los
valores asignados, en lugar de quedarse sólo con el último como ocurre en el Map. Podemos imaginarlo
como que asocia a cada clave una colección de valores en lugar de un sólo valor. Dependiendo de la
11
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/SortedMultiset.html
12
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/TreeMultiset.html
13
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/Multimap.html
9. Colecciones II 17
implementación, esa colección admitirá duplicados (si lo que asocia con la clave es una lista de valores) o no
(si lo que asocia es un conjunto). Las clases implementadoras son ArrayListMultimap14 o
LinkedListMultimap15 para las que admiten duplicados y HashMultimap16 para la que no.
se obtiene la salida
Hay que tener en consideración que los valores asociados a las claves son vistas. Si se añade al código
anterior
mm2.get("2").add("10 - 8");
mm2.get("2").remove("2");
mm2.get("3").clear();
mostrar("Modificaciones directas sobre el último:");
mostrar(mm2);
se obtiene
14
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/ArrayListMultimap.html
15
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/LinkedListMultimap.html
16
http://docs.guava-libraries.googlecode.com/git-
history/release/javadoc/com/google/common/collect/HashMultimap.html
18 Fundamentos de Programación
Map<K,Collection<V>> asMap()
Returns a map view that associates each key with the corresponding values in the
multimap.
void clear()
Returns true if the multimap contains any values for the specified key.
Returns true if the multimap contains the specified value for any key.
Collection<Map.Entry entries()
<K,V>>
Returns a collection of all key-value pairs.
Returns a collection view containing the values associated with key in this
multimap, if any.
boolean isEmpty()
Multiset<K> keys()
Set<K> keySet()
Returns the set of all keys, each appearing once in the returned set.
Stores a collection of values with the same key, replacing any existing values for that
key.
int size()
Collection<V> values()
Existe una clase de utilidad Multimaps, pero no tiene utilidad para nosotros por el momento.
7. Ejercicios propuestos
1. Escriba el método
de la clase Vuelos que dada una lista de vuelos devuelve una función que hace corresponder a cada
ciudad de destino la suma del número de pasajeros de todos los vuelos que tengan ese destino. Nota: no
usar Guava.
2. Escriba el método
que recibe una cadena de caracteres que representa una frase y devuelve una función ordenada que
hace corresponder a cada palabra de la frase (una vez convertida a minúsculas) la frecuencia de
aparición de esa palabra en la frase. Las claves (las palabras del texto) siguen el orden natural de las
palabras. Utilícese la cadena "\\W | " como delimitador en Cadenas.separaElementos.
"Este es el estribillo de una chirigota de Cádiz: Como como como como, estoy como
estoy"
{chirigota=1, como=5, cádiz=1, de=2, el=1, es=1, este=1, estoy=2, estribillo=1, una=1}
Obsérvese que las palabras están en orden alfabético (las vocales con tilde van detrás de las vocales sin
tilde en el orden alfabético de las cadenas en Java). Nota: no usar Guava.
20 Fundamentos de Programación
que recibe la función ordenada generada en el ejercicio anterior y, a partir de la información que
contiene, genera otra que devuelve y que hace corresponder a cada frecuencia de palabras que aparecía
en la función ordenada recibida, una lista con todas las palabras que tienen esa frecuencia. Las claves de
la función ordenada devuelta (enteros) tienen que estar ordenadas de mayor a menor, de modo que
aparezcan en primer lugar las palabras más frecuentes. Utilice un Comparator si lo considera necesario.
En la frase del ejemplo anterior, la función ordenada devuelta debe ser:
{5=[como], 2=[de, estoy], 1=[chirigota, cádiz, el, es, este, estribillo, una]}
Observe que las palabras con la misma frecuencia siguen en las listas correspondientes en orden
alfabético, puesto que se obtuvieron de la función anterior, donde estaban ordenadas. Nota: no usar
Guava.
4. Reescriba el método del ejercicio 2 usando Guava y haciendo que devuelva un SortedMultiset<Integer>:
5. Reescriba el método del ejercicio 4 usando Guava y haciendo que reciba el SortedMultiset anterior y que
devuelva un Multimap<Integer, String>:
En este ejercicio no se exige que los elementos aparezcan en orden de mayor a menor frecuencia.
6. Escriba en la clase Vuelos un método que dada una lista de vuelos devuelve un Multimap que hace
corresponder cada origen con los códigos de los vuelos de la lista que tienen ese origen. El perfil del
método es: