4 Mapas¶
Introducción¶
Las colecciones de tipo Set tienen el inconveniente de tener que almacenar una copia exacta del elemento a buscar. Sin embargo, en la práctica es habitual que haya datos que se consideren clave, es decir, que identifican a cada objeto (el dni de las personas por ejemplo) de tal manera que buscan los datos en base a esa clave y por otro lado se almacenan el resto de los datos. Los mapas permiten definir colecciones de elementos que poseen pares de datos clave-valor. Esto se utiliza para localizar valores en función de la clave que poseen. Son muy interesantes y rápidos. Los mapas también son conocidos como diccionarios.
La interfaz Map<K,V>
es la raíz de todas las clases que implementan mapas. Hasta la versión 5, los mapas eran colecciones de pares clave-valor donde tanto la clave como el valor eran de tipo Object. Desde la versión versión 5, esta interfaz tiene dos genéricos: K
para el tipo de datos de la clave y V
para el tipo de los valores. Esta estructura de datos nos permite obtener el objeto V
muy rápidamente a partir de su clave K
.
Esta interfaz no hereda de Collection por lo que no tiene los métodos vistos anteriormente. La razón es que la obtención, búsqueda y borrado de elementos se hace de una manera muy distinta.
Las claves no se pueden repetir por lo que se implementan con una tabla hash para que no haya duplicados. Por lo tanto, la clase que se utilice como clave tiene que sobrescribir sus métodos equals()
y hashCode()
.
Veamos algunos métodos de esta interfaz:
boolean containsKey(Object key)
: devuelve true si el mapa contiene dicha clave.boolean containsValue(Object value)
: devuelve true si el mapa contiene dicho valor.V get(Object key)
: devuelve el valor asociado a la clave o null si no existe esa clave en el mapa.V getOrDefault(Object V, defaultValue)
: devuelve el valor asociado a la clave o defaultValue si no existe esa clave en el mapa.V put(K key, V value)
: añade un par clave-valor al mapa. Si ya había un valor para esa clave, se reemplaza. Devuelve el valor que tenía antes dicha clave o null si la clave no estaba en el mapa.V putIfAbsent(K key, V value)
: si la clave especificada no está ya asociada a un valor o está asignada a null, se le asocia el valor dado y devuelve null, en caso contrario, devuelve el valor previamente asociado con la clave.void putAll(Map<? extends K, ? extends V> m)
: añade los pares claves-valor del mapa m.V remove(Object key)
: elimina la clave y su valor asociado, el cual se devuelve. Si no existe dicha clave, devuelve null.
Existen varios tipos de implementaciones realizadas dentro de la plataforma Java:
HashMap
: esta implementación almacena las claves en una tabla hash. Es la implementación con mejor rendimiento de todas pero no garantiza ningún orden a la hora de realizar iteraciones.LinkedHashMap
: esta implementación almacena las claves en función del orden de la inserción. Es un poco más costosa queHashMap
.TreeMap
: esta implementación utiliza la estructura del árbol para ordenar las claves. Es bastante más lenta queHashMap
. La veremos más adelante en el apartado de los árboles.
Clase HashMap¶
Esta implementación proporciona tiempos constantes en las operaciones básicas siempre y cuando la función hash disperse de forma correcta las claves dentro de la tabla hash. Es importante definir el tamaño inicial de la tabla ya que este tamaño marcará el rendimiento de esta implementación. No garantiza ningún orden a la hora de recorrer el mapa.
Veamos un ejemplo de un mapa de vehículos donde la clave es la matrícula. Añadimos la matrícula como atributo por lo que hay generar de nuevo los métodos toString()
, hashCode()
y equals()
:
public class Vehicle{
private final String registration; // Atributo para almacenar la matrícula del coche
private final int wheelCount;
private double speed;
private String colour;
public Vehicle(String registration, int wheelCount, String colour){
this.registration = registration;
this.wheelCount = wheelCount;
this.colour = colour;
speed = 0;
}
public int getWheelCount(){
return wheelCount;
}
public double getSpeed(){
return speed;
}
public String getColour(){
return colour;
}
public void setColour(String colour){
this.colour = colour;
}
public String getRegistration(){
return registration;
}
public void accelerate(double amount){
speed += amount;
}
public void brake(double amount){
speed -= amount;
}
@Override
public String toString(){
return "Vehicle [registration=" + registration + ", wheelCount=" + wheelCount + ", speed=" + speed + ", colour=" + colour + "]";
}
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + ((colour == null) ? 0 : colour.hashCode());
result = prime * result + ((registration == null) ? 0 : registration.hashCode());
result = prime * result + wheelCount;
return result;
}
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null) return false;
if(!(obj instanceof Vehicle other)) return false;
if(colour == null){
if(other.colour != null){
return false;
}
} else if(!colour.equals(other.colour)){
return false;
}
if(registration == null){
if(other.registration != null){
return false;
}
} else if(!registration.equals(other.registration)){
return false;
}
return wheelCount == other.wheelCount;
}
}
public class ShowHashMap {
public void show() {
Map<String, Vehicle> map = new HashMap<>();
Map<String, Vehicle> map2 = new HashMap<>();
Vehicle vehicles[] = new Vehicle[6];
vehicles[0] = new Vehicle("9685KMX", 4, "azul");
vehicles[1] = new Vehicle("1235GTR", 2, "rojo");
vehicles[2] = new Vehicle("7314QWE", 4, "verde");
vehicles[3] = new Vehicle("5930POI", 2, "negro");
vehicles[4] = new Vehicle("1705UBG", 4, "blanco");
vehicles[5] = new Vehicle("3495JZA", 2, "naranja");
for (int i = 0; i < vehicles.length; i++) {
map.put(vehicles[i].getRegistration(), vehicles[i]);
}
System.out.println(map.containsKey("1005SAW"));//false
System.out.println(map.containsKey("1705UBG"));//true
System.out.println(map.containsValue(new Vehicle("5930POI", 4,"negro")));//false
System.out.println(map.containsValue(new Vehicle("5930POI", 2,"negro")));//true
System.out.println(map.get("4554ASD"));//null
System.out.println(map.get("1705UBG"));//Vehicle[registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
System.out.println(map.getOrDefault("8080SAS", new Vehicle("4554ASD", 4, "negro")));//Vehicle [registration=4554ASD, wheelCount=4, speed=0.0,colour=negro]
System.out.println(map.getOrDefault("1705UBG", new Vehicle("4554ASD", 4, "negro")));//Vehicle [registration=1705UBG, wheelCount=4, speed=0.0,colour=blanco]
System.out.println(map.put("6320LPL", new Vehicle("6320LPL", 2,
"verde")));//null
System.out.println(map.put("6320LPL", new Vehicle("6320LPL", 4,
"beis")));//Vehicle [registration=6320LPL, wheelCount=2, speed=0.0,colour=verde]
System.out.println(map.putIfAbsent("4687RTB", new Vehicle("4687RTB", 2, "blanco")));//null
System.out.println(map.putIfAbsent("4687RTB", new Vehicle("4687RTB", 4, "naranja")));//Vehicle [registration=4687RTB, wheelCount=2, speed=0.0,colour=blanco]
System.out.println(map.remove("1234ABC"));//null
System.out.println(map.remove("4687RTB"));//Vehicle[registration=4687RTB, wheelCount=2, speed=0.0, colour=blanco]
System.out.printf("El mapa tiene %d vehículos", map.size());
map2.put("7410HJH", new Vehicle("7410HJH", 4, "rojo"));
map2.put("8520FGF", new Vehicle("8520FGF", 2, "verde"));
map.putAll(map2);//añade a map los pares clave-valor del mapa map2
System.out.printf("\nDespués de añadirle map2, el mapa tiene %d vehículos", map.size());
}
public static void main(String[] args) {
new ShowHashMap().show();
}
}
false
true
false
true
null
Vehicle [registration=1705UBG, wheelCount=4, speed=0.0 colour=blanco]
Vehicle [registration=4554ASD, wheelCount=4, speed=0.0, colour=negro]
Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
null
Vehicle [registration=6320LPL, wheelCount=2, speed=0.0, colour=verde]
null
Vehicle [registration=4687RTB, wheelCount=2, speed=0.0, colour=blanco]
null
Vehicle [registration=4687RTB, wheelCount=2, speed=0.0, colour=blanco]
El mapa tiene 7 vehículos
Después de añadirle map2, el mapa tiene 9 vehículos
Veamos las distintas maneras de recorrer un mapa:
Set<K> keySet()
: devuelve un conjunto con todas las claves. como entre las claves no puede haber elementos duplicados, las claves forman un conjunto (Set).Collection<V> values()
: devuelve una colección con todos los valores. Los valores sí pueden estar duplicados, por lo tanto, este método devuelve un Collection.Set<Map.Entry<K,V>> entrySet
: devuelve un conjunto de objetos Map.Entry. Los pares de elementos (también llamados entradas) de los que está compuesto un Map son de un tipo que viene implementado por la interfaz Map.Entry . La interfaz Map.Entry se define de forma interna a la interfaz Map y representa un objeto de par clave-valor, es decir, mediante esta interfaz podemos trabajar con una entrada del mapa. Veamos algunos métodos de la interfaz Map.Entry . K getKey()
: retorna la clave.V getValue()
: retorna el valor.V setValue(V value)
: reemplaza el valor por value y devuelve el valor anterior.
public class TraverseHashMap {
public void show() {
Map<String, Vehicle> map = new HashMap<>();
Vehicle vehicles[] = new Vehicle[6];
vehicles[0] = new Vehicle("9685KMX", 4, "azul");
vehicles[1] = new Vehicle("1235GTR", 2, "rojo");
vehicles[2] = new Vehicle("7314QWE", 4, "verde");
vehicles[3] = new Vehicle("5930POI", 2, "negro");
vehicles[4] = new Vehicle("1705UBG", 4, "blanco");
vehicles[5] = new Vehicle("3495JZA", 2, "naranja");
for (int i = 0; i < vehicles.length; i++) {
map.put(vehicles[i].getRegistration(), vehicles[i]);
}
System.out.println("Claves del mapa:\n");
for (String s : map.keySet()) {//keySet() devuelve un conjunto con todas las claves
System.out.println(s);
}
System.out.println("\nValores del mapa:\n");
for (Vehicle v : map.values()) {//values() devuelve una colección con todos los vehículos
System.out.println(v);
}
System.out.println("\nPares clave-valor del mapa usando un foreach:\n");
for (Map.Entry<String, Vehicle> entry : map.entrySet()) {
System.out.printf("Matrícula -> %s Vehículo -> %s\n",entry.getKey(), entry.getValue());
}
System.out.println("\nPares clave-valor del mapa usando iteradores:\n");
Set<Map.Entry<String, Vehicle>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Vehicle>> it = entrySet.iterator();
Map.Entry<String, Vehicle> entry;
while (it.hasNext()) {
entry = it.next();
System.out.printf("Matrícula -> %s Vehículo -> %s\n",
entry.getKey(), entry.getValue());
}
}
public static void main(String[] args) {
new TraverseHashMap().show();
}
}
Claves del mapa:
3495JZA
1705UBG
1235GTR
7314QWE
9685KMX
5930POI
Valores del mapa:
Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=naranja]
Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo]
Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul]
Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro]
Pares clave-valor del mapa usando un foreach:
Matrícula -> 3495JZA Vehículo -> Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=naranja]
Matrícula -> 1705UBG Vehículo -> Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
Matrícula -> 1235GTR Vehículo -> Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo]
Matrícula -> 7314QWE Vehículo -> Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Matrícula -> 9685KMX Vehículo -> Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul]
Matrícula -> 5930POI Vehículo -> Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro]
Pares clave-valor del mapa usando iteradores:
Matrícula -> 3495JZA Vehículo -> Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=naranja]
Matrícula -> 1705UBG Vehículo -> Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
Matrícula -> 1235GTR Vehículo -> Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo]
Matrícula -> 7314QWE Vehículo -> Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Matrícula -> 9685KMX Vehículo -> Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul]
Matrícula -> 5930POI Vehículo -> Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro]
Clase LinkedHashMap¶
Almacena las claves en función del orden de inserción. Es un poco más costosa que HashMap
.
public class ShowLinkedHashMap {
public void show() {
Map<String, Vehicle> map = new LinkedHashMap<>();
Vehicle vehicles[] = new Vehicle[6];
vehicles[0] = new Vehicle("9685KMX", 4, "azul");
vehicles[1] = new Vehicle("1235GTR", 2, "rojo");
vehicles[2] = new Vehicle("7314QWE", 4, "verde");
vehicles[3] = new Vehicle("5930POI", 2, "negro");
vehicles[4] = new Vehicle("1705UBG", 4, "blanco");
vehicles[5] = new Vehicle("3495JZA", 2, "naranja");
for (int i = 0; i < vehicles.length; i++) {
map.put(vehicles[i].getRegistration(), vehicles[i]);
}
for (Map.Entry<String, Vehicle> entry : map.entrySet()) {
System.out.printf("Matrícula -> %s Vehículo -> %s\n",entry.getKey(), entry.getValue());
}
}
public static void main(String[] args) {
new ShowLinkedHashMap().show();
}
}
Matrícula -> 9685KMX Vehículo -> Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul]
Matrícula -> 1235GTR Vehículo -> Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo]
Matrícula -> 7314QWE Vehículo -> Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Matrícula -> 5930POI Vehículo -> Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro]
Matrícula -> 1705UBG Vehículo -> Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco]
Matrícula -> 3495JZA Vehículo -> Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=naranja]
Podemos observar que los datos se muestran en el mismo orden en el que se insertaron.
Ejemplo de uso de un mapa en un enum¶
Si hemos sobrescrito el método toString()
o las instancias del enum tienen alguna forma adicional de referirnos a ellas, tiene bastante sentido que creemos un método estático parecido a valueOf()
, pero que reciba dicha forma adicional de referirnos a las instancias.
public enum Operation {
PLUS("+") {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
private static final Map<String, Operation> symbolToOperation = Map.of(Operation.PLUS.getSymbol(), Operation.PLUS, Operation.MINUS.getSymbol(), Operation.MINUS, Operation.TIMES.getSymbol(), Operation.TIMES,Operation.DIVIDE.getSymbol(), Operation.DIVIDE);
private Operation(String symbol) {
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
public abstract double apply(double x, double y);
public static Operation fromSymbol(String symbol) {
return symbolToOperation.get(symbol);
}
}
public class ExampleUseMapEnum {
public void show() {
Operation operation;
operation = Operation.fromSymbol("+");//operation se asigna con la instancia correspondiente al símbolo +
System.out.printf("La variable operation es de tipo enum %s y su símbolo es %s", operation, operation.getSymbol());
}
public static void main(String[] args) {
new ExampleUseMapEnum().show();
}
}
Como vemos en el código anterior, creamos un mapa estático que relaciona cada símbolo con cada instancia, de manera que podamos obtener la instancia adecuada a partir del símbolo. Debemos tener en cuenta que no está permitido que los constructores de las instancias de un enum accedan a los atributos estáticos del enum, con la excepción de las constantes de las instancias, dado que los atributos estáticos aún no han sido inicializados cuando se están ejecutando los constructores de las instancias. Un caso especial de esta restricción es que en los constructores de las instancias tampoco se puede acceder a otras instancias del enum.
Clases EnumMap¶
Es una implementación de mapa muy eficiente donde las claves son elementos de una enumeración:
public class ShowEnumMap {
public void show() {
EnumMap<Operation, String> operationsMap = new EnumMap<>(Operation.class);
operationsMap.put(Operation.PLUS, "Esta operación se utiliza para sumar");
operationsMap.put(Operation.MINUS, "Esta operación se utiliza para restar");
operationsMap.put(Operation.TIMES, "Esta operación se utiliza para multiplicar");
operationsMap.put(Operation.DIVIDE, "Esta operación se utiliza para dividir");
for (Map.Entry<Operation, String> entry : operationsMap.entrySet()) {
System.out.printf("%-6s: %s\n", entry.getKey(), entry.getValue());
}
}
public static void main(String[] args) {
new ShowEnumMap().show();
}
}