🗺️ Map - Mapa en Java
📌 1. ¿Qué es un Map?
Map en Java es una interfaz.
Es decir:
- no es una clase
- no se puede instanciar directamente
- define un contrato/estándar para trabajar con parejas clave → valor
Piensa en ello como una agenda:
- la clave es el nombre
- el valor es el teléfono
Ejemplo conceptual:
"Ana" -> 666111222
"Luis" -> 644222333
"Marta" -> 611333444
Ejemplo real en Java:
Map<String, Integer> edades = new HashMap<>();
Aquí:
Map<String, Integer>es la interfazHashMap<>es la clase que la implementa
🚫 1.1 Map no hereda de Collection
Esto es importante:
Mapforma parte del framework de colecciones de Java, pero NO hereda deCollection.
¿Por qué?
Porque Collection representa una colección “simple” de elementos:
- una
Listguarda elementos - un
Setguarda elementos
Pero un Map no guarda elementos sueltos, sino pares clave-valor.
O sea, un mapa no almacena solo una cosa:
"Ana"
sino una asociación completa:
"Ana" -> 20
Por eso Java lo separa de Collection.
Aun así, sí puedes obtener vistas del mapa que sí son colecciones:
keySet()→ devuelve unSetcon las clavesvalues()→ devuelve unaCollectioncon los valoresentrySet()→ devuelve unSetde entradas clave-valor
🔑 Idea clave
En un Map:
- las claves NO se repiten, son ÚNICAS en el mapa
- los valores sí pueden repetirse
Ejemplo:
Map<String, Integer> edades = new HashMap<>();
edades.put("Ana", 20);
edades.put("Luis", 25);
edades.put("Marta", 20); // esto sí se puede
Pero esto no:
edades.put("Ana", 20);
edades.put("Ana", 30);
No da error, pero la segunda operación sobrescribe el valor anterior.
🧠 2. ¿Para qué sirve un mapa?
Sirve cuando quieres buscar algo a partir de una clave.
Casos típicos:
- DNI → persona
- matrícula → coche
- usuario → contraseña
- palabra → definición
- producto → precio
- alumno → nota
Si con una lista tenías que recorrer elemento a elemento, con un mapa normalmente buscas directamente por clave.
📦 3. Implementaciones principales de Map
Las más importantes son:
HashMapLinkedHashMapTreeMap
⚔️ Comparativa rápida
| Implementación | Orden | Velocidad habitual | Permite null |
Cuándo usarla |
|---|---|---|---|---|
HashMap |
No garantiza orden | Muy rápida | Sí | Cuando quieres buscar rápido |
LinkedHashMap |
Mantiene orden de inserción | Muy rápida | Sí | Cuando quieres rapidez y orden |
TreeMap |
Ordena por clave | Más lenta | No clave null |
Cuando quieres los datos ordenados |
🚀 4. HashMap: la implementación más importante
✅ Qué es
HashMap es la implementación más usada de Map.
Su objetivo principal es este:
buscar por clave muy rápido.
Ejemplo:
Map<String, Integer> notas = new HashMap<>();
notas.put("Ana", 8);
notas.put("Luis", 6);
notas.put("Marta", 9);
System.out.println(notas.get("Luis")); // 6
⚙️ 5. ¿Cómo funciona HashMap por dentro?
Esta es la idea sencilla:
🪣 Paso 1: tiene “cubos” o “buckets”
Por dentro, HashMap organiza la información en una tabla.
Puedes imaginar algo así:
[0] -> ...
[1] -> ...
[2] -> ...
[3] -> ...
[4] -> ...
Cada posición es un bucket.
🧮 Paso 2: usa hashCode() de la clave
Cuando metes una clave, Java calcula su hashCode().
Con ese número decide en qué bucket guardarla.
📥 Paso 3: guarda la pareja clave-valor allí
Cuando haces:
map.put("Ana", 8);
Java:
- calcula el hash de
"Ana" - decide bucket
- guarda ahí la entrada
"Ana" -> 8
📤 Paso 4: al buscar, vuelve a calcular
Cuando haces:
map.get("Ana");
Java:
- vuelve a calcular el hash de
"Ana" - va directamente al bucket correspondiente
- compara las claves con
equals() - devuelve el valor
🎯 6. Entonces, ¿por qué es tan rápido?
Porque no necesita recorrer todo como una lista.
En vez de buscar así:
Ana? no
Luis? no
Marta? sí
va casi directo al sitio correcto.
Por eso, en condiciones normales:
put()suele ser muy rápidoget()suele ser muy rápidoremove()suele ser muy rápido
Recuerda que HashMap no garantiza orden.
📉 7. Eficiencia de HashMap
⏱️ Complejidades típicas
| Operación | Coste habitual |
|---|---|
put(clave, valor) |
O(1) |
get(clave) |
O(1) |
remove(clave) |
O(1) |
| recorrer todo el mapa | O(n) |
⚠️ Importante
Ese O(1) es el caso habitual o esperado.
No significa “mágicamente instantáneo siempre”.
Si hay muchas colisiones, el rendimiento puede empeorar.
💥 8. ¿Qué es una colisión?
Una colisión ocurre cuando dos claves distintas terminan en el mismo bucket.
Ejemplo imaginario:
"Ana" -> bucket 3
"Luis" -> bucket 3
Ambas claves caen en el mismo sitio.
Entonces HashMap tiene que guardar varias entradas en ese bucket.
Eso hace que buscar sea menos eficiente.
Usar muchas claves con el mismo hashCode() empeora el rendimiento.
🧩 9. ¿Qué papel tienen equals() y hashCode()?
En un HashMap, la clave se maneja con estos dos métodos:
hashCode()decide a qué zona aproximada irequals()decide si esa clave es exactamente la correcta
✅ Regla de oro
Si dos objetos son iguales según equals(), deben tener el mismo hashCode().
Si no, el mapa puede comportarse mal.
❌ 10. Error típico con claves personalizadas
class Alumno {
private String dni;
public Alumno(String dni) {
this.dni = dni;
}
}
Y luego:
Map<Alumno, String> mapa = new HashMap<>();
mapa.put(new Alumno("111A"), "Ana");
System.out.println(mapa.get(new Alumno("111A")));
Muchos pensaréis que devolverá Ana, pero probablemente devolverá null, porque es otro objeto y no hemos sobrescrito el método hashCode().
🤔 ¿Por qué?
Porque son dos objetos distintos.
Si no redefinimos equals() y hashCode(), Java los trata como diferentes.
✅ 11. Claves mutables: peligro real
Esto es importantísimo.
Si usas como clave un objeto y cambias después el atributo que participa en equals() o hashCode(), puedes “romper” el mapa.
Ejemplo conceptual:
Map<Cuenta, String> mapa = new HashMap<>();
Cuenta c = new Cuenta("ES123");
mapa.put(c, "Cuenta principal");
c.setIban("ES999");
Ahora puede pasar que:
- el objeto siga dentro del mapa
- pero no lo encuentres bien con
get()ocontainsKey()
Hay que tener muchísimo cuidado con claves mutables y que el comportamiento deja de estar bien definido si cambias una clave de una forma que afecte a equals().
✅ Conclusión
Las claves en mapas deberían ser preferiblemente:
- inmutables
- bien definidas
- con
equals()yhashCode()correctos
🛠️ 12. Operaciones básicas de Map
➕ put()
Añade o sustituye una entrada. Devuelve el valor anterior asociado a esa clave. Si la clave no existía, devuelve null.
Map<String, Integer> mapa = new HashMap<>();
mapa.put("Ana", 8);
mapa.put("Luis", 6);
mapa.put("Ana", 10); // sustituye el valor anterior
🔍 get()
Devuelve el valor asociado a la clave. Si la clave no existe → devuelve null.
System.out.println(mapa.get("Ana")); // 10
❓ containsKey()
Comprueba si existe una clave, devuelve true or false.
System.out.println(mapa.containsKey("Luis")); // true
❓ containsValue()
Comprueba si existe un valor. Devuelve true si ese valor aparece en alguna entrada del mapa o false si no aparece.
System.out.println(mapa.containsValue(10)); // true
🗑️ remove()
Elimina una entrada por clave. Devuelve el valor que estaba asociado a esa clave. Si la clave existía → devuelve el valor eliminado. Si no existía → devuelve null
mapa.remove("Luis");
📏 size()
Devuelve el número de entradas.
System.out.println(mapa.size());
🧹 clear()
Vacía el mapa.
mapa.clear();
👀 13. Cómo recorrer un mapa
1) Recorrer claves con keySet()
for (String clave : mapa.keySet()) {
System.out.println(clave);
}
2) Recorrer valores con values()
for (Integer valor : mapa.values()) {
System.out.println(valor);
}
3) Recorrer clave y valor con entrySet() ✅
Esta suele ser la mejor opción si necesitas ambas cosas.
for (Map.Entry<String, Integer> entrada : mapa.entrySet()) {
System.out.println(entrada.getKey() + " -> " + entrada.getValue());
}
🧪 14. Ejemplo completo sencillo
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> notas = new HashMap<>();
notas.put("Ana", 8);
notas.put("Luis", 6);
notas.put("Marta", 9);
notas.put("Luis", 7); // sustituye el 6
System.out.println("Nota de Marta: " + notas.get("Marta"));
System.out.println("¿Existe Ana? " + notas.containsKey("Ana"));
System.out.println("Tamaño: " + notas.size());
for (Map.Entry<String, Integer> e : notas.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}
}
}
🧾 15. ¿Se permiten claves y valores null en HashMap?
Sí:
- permite UNA clave
null - permite varios valores
null
Map<String, Integer> mapa = new HashMap<>();
mapa.put(null, 10);
mapa.put("Ana", null);
⚠️ 16. Diferencia importante con Set
Set
Guarda solo valores:
[ana, luis, marta]
Map
Guarda pares clave-valor:
ana -> 8
luis -> 6
marta -> 9
De hecho:
- un
Sette dice si existe un elemento - un
Mapte permite obtener el valor asociado a una clave
🧠 17. Cosas modernas y útiles de Java sobre mapas
Aquí van varias cosas importantes que conviene conocer.
1) getOrDefault()
Muy útil cuando la clave podría no existir.
Map<String, Integer> mapa = new HashMap<>();
mapa.put("Ana", 8);
System.out.println(mapa.getOrDefault("Luis", 0)); // 0
En vez de devolver null, te da un valor por defecto.
2) putIfAbsent()
Solo mete el valor si la clave no estaba ya.
mapa.putIfAbsent("Ana", 99); // no cambia nada
mapa.putIfAbsent("Luis", 5); // sí lo añade
3) replace()
Reemplaza un valor solo si la clave existe.
mapa.replace("Ana", 10);
4) forEach()
Más cómodo para recorrer:
mapa.forEach((k, v) -> System.out.println(k + " -> " + v));
5) computeIfAbsent()
Muy útil para mapas de listas o contadores.
Map<String, List<String>> grupos = new HashMap<>();
grupos.computeIfAbsent("DAM", k -> new ArrayList<>()).add("Ana");
grupos.computeIfAbsent("DAM", k -> new ArrayList<>()).add("Luis");
Esto evita hacer:
if (!grupos.containsKey("DAM")) {
grupos.put("DAM", new ArrayList<>());
}
6) merge()
Buenísimo para contar cosas.
Map<String, Integer> contador = new HashMap<>();
contador.merge("rojo", 1, Integer::sum);
contador.merge("rojo", 1, Integer::sum);
contador.merge("azul", 1, Integer::sum);
System.out.println(contador); // {rojo=2, azul=1}
| Método | ¿Usa hashCode()? |
¿Usa equals()? |
Explicación |
|---|---|---|---|
put(clave, valor) |
Sí | Sí | Calcula el hashCode() de la clave para ir al cubo correspondiente. Si ya hay claves en ese cubo, compara con equals() para ver si la clave ya existía y sustituir el valor. |
get(clave) |
Sí | Sí | Usa hashCode() para localizar el cubo y luego equals() para encontrar la clave exacta dentro de ese cubo. |
containsKey(clave) |
Sí | Sí | Igual que get(): localiza por hashCode() y confirma con equals(). |
remove(clave) |
Sí | Sí | Busca la clave usando hashCode() y equals(), y si la encuentra elimina esa entrada. |
putIfAbsent(clave, valor) |
Sí | Sí | Necesita comprobar si la clave ya existe, así que funciona igual que una búsqueda por clave. |
getOrDefault(clave, valorPorDefecto) |
Sí | Sí | Busca la clave con el mismo mecanismo que get(). |
replace(clave, valor) |
Sí | Sí | Primero debe localizar la clave; para eso usa hashCode() y equals(). |
computeIfAbsent(clave, funcion) |
Sí | Sí | Comprueba si la clave existe antes de calcular el valor. |
computeIfPresent(clave, funcion) |
Sí | Sí | Busca la clave para ver si está presente. |
compute(clave, funcion) |
Sí | Sí | Trabaja sobre una clave concreta, así que antes debe localizarla. |
merge(clave, valor, funcion) |
Sí | Sí | Busca si esa clave ya existe para fusionar o insertar. |
🆕 18. Map.of(...), Map.ofEntries(...) y Map.copyOf(...)
Estas utilidades modernas permiten crear mapas inmutables de forma muy cómoda.
✅ Map.of(...)
Map<String, Integer> mapa = Map.of(
"Ana", 8,
"Luis", 6,
"Marta", 9
);
✅ Map.ofEntries(...)
Útil cuando hay muchas entradas.
Map<String, Integer> mapa = Map.ofEntries(
Map.entry("Ana", 8),
Map.entry("Luis", 6),
Map.entry("Marta", 9)
);
✅ Map.copyOf(...)
Crea una copia inmutable a partir de otro mapa.
Map<String, Integer> original = new HashMap<>();
original.put("Ana", 8);
Map<String, Integer> copia = Map.copyOf(original);
⚠️ Ojo
Estos mapas son no modificables, no podrías realizar la siguiente operación:
mapa.put("Pepe", 4); // UnsupportedOperationException
🧠 19. Consejos prácticos para elegir mapa
Usa HashMap cuando...
- quieras rapidez
- el orden te dé igual
- sea tu opción por defecto
🚫 20. Errores típicos del alumnado
❌ Pensar que HashMap mantiene orden
No. Puede coincidir alguna vez, pero no está garantizado.
❌ Pensar que put() con misma clave añade otra entrada
No. Sustituye el valor.
❌ Usar objetos como clave sin equals() y hashCode()
Eso rompe búsquedas lógicas.
❌ Cambiar una clave después de insertarla
Muy peligroso si afecta al hash o a la igualdad.
❌ Recorrer con keySet() y luego hacer get() si ya necesitas ambas cosas
Si necesitas clave y valor, suele ser mejor entrySet().
🧩 21. Crear un mapa de mapas
A veces, en lugar de guardar un valor simple, queremos guardar otro mapa como valor.
Eso se llama un mapa anidado o mapa de mapas.
//Ejemplo: //La clave externa es un String y el valor asociado es otro Map
//En ese mapa interno: la clave es un String y el valor es un Integer
Map<String, Map<String, Integer>> notas = new HashMap<>();
Map<String, Integer> eva1 = new HashMap<>();
eva1.put("Ana", 8);
eva1.put("Luis", 6);
Map<String, Integer> eva2 = new HashMap<>();
eva2.put("Ana", 9);
eva2.put("Luis", 7);
notas.put("Primera", eva1);
notas.put("Segunda", eva2);
🔍 Acceder a un dato de un mapa de mapas:
Para acceder, hacemos un get() del mapa exterior y luego otro get() del mapa interior:
System.out.println(notas.get("Primera").get("Ana")); // 8
System.out.println(notas.get("Segunda").get("Luis")); // 7
Para modificar un dato del mapa interno:
notas.get("Primera").put("Ana", 10);
//o si lo quieres hacer en pasos separados
Map<String, Integer> eva = notas.get("Primera");
eva.put("Ana",10);
✅ 22. Conclusión
- una
Listbusca por posición - un
Setguarda valores únicos - un
Maprelaciona una clave con un valor