HashMap: El Diccionario Supersónico de Java
Introducción: El Guardarropa de la Discoteca
¡Qué pasa, equipo! 🎧
Imaginad que llegáis a una discoteca en invierno. Lleváis un abrigo enorme. Vais al guardarropa, entregáis el abrigo y os dan un ticket con un número (por ejemplo, el 105).
Cuando queréis irnos, no le decís al encargado: "Oye, dame mi abrigo, es uno azul que tiene un botón medio suelto...". ¡No! Le dais el ticket 105. El encargado va directo a la percha 105 y te lo da. Tiempo de búsqueda: Instantáneo.
Esto es un HashMap. * El Ticket es la KEY (Clave). Única e irrepetible. * El Abrigo es el VALUE (Valor). Lo que guardas. * El Guardarropa es el MAP (Mapa).
Si usáramos un ArrayList para esto, el encargado tendría que mirar todos los abrigos uno por uno hasta encontrar el tuyo (lento, ineficiente y acabaríais congelados). Con HashMap, el acceso es directo y veloz. 🚀
Conceptos Fundamentales
Definición: Map (Mapa)
Es una interfaz que asigna Claves (Keys) a Valores (Values). No es una Collection tradicional (no hereda de Collection), es una estructura paralela.
* Regla de Oro: No puede contener claves duplicadas. Cada clave tiene asociado, como máximo, un valor.
Definición: HashMap
Es la implementación más usada de Map.
* Hash: Usa el código hash de la clave para calcular dónde guardar el valor en memoria.
* Orden: NO garantiza orden. Si metes datos hoy, mañana pueden salir en otro orden.
* Nulls: Permite una clave null y múltiples valores null.
Mapa Mental: La Estructura K-V
Desarrollo y Ejemplos Prácticos
Vamos a ver los métodos agrupados por funcionalidad. Recordad: aquí trabajamos con parejas.
1. Operaciones Básicas (CRUD)
put(K key, V value): Inserta la pareja. Si la clave ya existía, machaca el valor antiguo y lo devuelve.get(Object key): Busca por clave y devuelve el valor. Si no existe, devuelvenull.remove(Object key): Borra la pareja asociada a esa clave.size()/isEmpty()/clear(): Lo de siempre.
Ejemplo: La Agenda de Contactos
import java.util.HashMap;
import java.util.Map;
public class AgendaContactos {
public static void main(String[] args) {
// <Clave: Nombre (String), Valor: Telefono (Integer)>
// Usamos la interfaz Map a la izquierda (Buenas prácticas)
Map<String, Integer> agenda = new HashMap<>();
// 1. PUT: Añadir contactos
agenda.put("Elon Musk", 666111222);
agenda.put("Jeff Bezos", 666999888);
agenda.put("Mi Madre", 600000000);
// 2. PUT (Actualizar): Elon cambia de número
// Al usar la misma clave, se sobrescribe el valor.
Integer antiguoNumero = agenda.put("Elon Musk", 123456789);
System.out.println("Número antiguo de Elon: " + antiguoNumero);
// 3. GET: Recuperar datos
Integer telefonoMadre = agenda.get("Mi Madre");
System.out.println("Llamando a mamá: " + telefonoMadre);
// Intentar recuperar alguien que no existe
System.out.println("Télefono de Batman: " + agenda.get("Batman")); // null
// 4. REMOVE: Borrar contacto
agenda.remove("Jeff Bezos"); // Adiós Jeff
System.out.println("Total contactos: " + agenda.size()); // 2
System.out.println("Agenda: " + agenda);
}
}
2. Verificaciones (El "Check")
Antes de actuar, a veces hay que preguntar.
containsKey(Object key): Fundamental. Pregunta si la clave existe. Rapidez: O(1).containsValue(Object value): Pregunta si el valor existe. Cuidado: Es lento O(n) porque tiene que mirar todos los valores uno a uno.
Ejemplo: Login de Usuarios
import java.util.HashMap;
public class LoginSystem {
public static void main(String[] args) {
// <Usuario, Contraseña>
HashMap<String, String> credenciales = new HashMap<>();
credenciales.put("admin", "1234");
credenciales.put("invitado", "guest");
String usuarioIntento = "hacker";
// containsKey es super eficiente
if (credenciales.containsKey(usuarioIntento)) {
System.out.println("Usuario encontrado. Verificando password...");
} else {
System.out.println("Error: El usuario no existe.");
}
// containsValue (Más lento, evitar en mapas gigantes)
boolean existePasswordInsegura = credenciales.containsValue("1234");
System.out.println("¿Hay passwords débiles? " + existePasswordInsegura);
}
}
3. Iteración: Recorriendo el Mapa
Como un mapa no es una lista, no tiene un índice numérico 0..n. Tenemos tres formas de verlo (Vistas):
1. keySet(): Conjunto de Claves.
2. values(): Colección de Valores.
3. entrySet(): Conjunto de Parejas (Entry). (La más eficiente).
Además, desde Java 8 tenemos el maravilloso forEach.
import java.util.HashMap;
import java.util.Map;
public class InventarioFruta {
public static void main(String[] args) {
Map<String, Double> precios = new HashMap<>();
precios.put("Manzana", 1.50);
precios.put("Pera", 2.00);
precios.put("Kiwis", 3.50);
System.out.println("--- 1. Iterar solo Claves (keySet) ---");
for (String fruta : precios.keySet()) {
System.out.println("Vendemos: " + fruta);
}
System.out.println("--- 2. Iterar solo Valores (values) ---");
for (Double precio : precios.values()) {
System.out.println("Precio: " + precio + "€");
}
System.out.println("--- 3. Iterar Todo (entrySet) - EFICIENTE ---");
for (Map.Entry<String, Double> entrada : precios.entrySet()) {
String k = entrada.getKey();
Double v = entrada.getValue();
System.out.println("La " + k + " cuesta " + v + "€");
}
System.out.println("--- 4. Java 8 forEach (Lambda) - MODERNO ---");
precios.forEach((fruta, precio) -> {
System.out.println("Oferta: " + fruta + " a " + (precio * 0.9) + "€");
});
}
}
4. Métodos "Inteligentes" (Java 8+)
Estos métodos os ahorrarán cientos de if-else y comprobaciones de nulos. Son la marca de un programador senior.
putIfAbsent(K, V): Solo inserta si la clave no existía (o valía null).getOrDefault(K, V_defecto): Dame el valor, y si no existe, dame este valor por defecto (para evitar nulos).replace(K, V): Actualiza solo si la clave ya existe.computeIfAbsent(K, Function): Si no existe, calcúlalo y guárdalo (Lazy loading).
Ejemplo: Contador de Palabras (Word Frequency)
Imaginad que queréis contar cuántas veces aparece cada palabra en una lista.
import java.util.HashMap;
import java.util.Map;
public class ContadorPalabras {
public static void main(String[] args) {
String[] texto = {"java", "code", "java", "coffee", "code", "java"};
Map<String, Integer> contador = new HashMap<>();
for (String palabra : texto) {
// FORMA ANTIGUA (Muy verbosa)
/*
if (contador.containsKey(palabra)) {
int actual = contador.get(palabra);
contador.put(palabra, actual + 1);
} else {
contador.put(palabra, 1);
}
*/
// FORMA MODERNA 1: merge
// Si existe, suma 1 al valor actual. Si no, pon 1.
contador.merge(palabra, 1, Integer::sum);
// FORMA MODERNA 2: getOrDefault (Alternativa común)
// contador.put(palabra, contador.getOrDefault(palabra, 0) + 1);
}
System.out.println("Frecuencias: " + contador);
// {code=2, java=3, coffee=1}
// --- Ejemplos de putIfAbsent y replace ---
// Solo añadimos "python" si no estaba
contador.putIfAbsent("java", 100); // No hace nada, java ya existe
contador.putIfAbsent("python", 1); // Añade python=1
// Replace: Corregimos "coffee" solo si existe
contador.replace("coffee", 99);
contador.replace("c++", 5); // No hace nada, c++ no existe
System.out.println("Final: " + contador);
}
}
5. Otros Métodos
clone(): Crea una copia superficial (shallow copy). DevuelveObject.
HashMap<Integer, String> mapOriginal = new HashMap<>();
mapOriginal.put(1, "Uno");
HashMap<Integer, String> mapCopia = (HashMap<Integer, String>) mapOriginal.clone();
Diagrama Mermaid: ¿Cómo funciona put internamente?
Cuando hacéis put("Clave", "Valor"), Java no tira los datos al azar.
graph TD
A["put 'Clave', 'Valor'"] --> B["Calcular hashCode de 'Clave'"]
B --> C["Obtener índice del Array"]
C --> D["¿Hay algo en ese índice?"]
D -- No (Vacío) --> E["Crear Nodo y Guardar"]
D -- Sí (Colisión) --> F["Comparar claves con equals"]
F --> G["¿Son iguales?"]
G -- Sí --> H["Sobrescribir Valor Antiguo"]
G -- No --> I["Añadir al final de la lista enlazada/árbol en ese hueco"]
Tabla Comparativa: HashMap vs ArrayList
| Característica | ArrayList | HashMap |
|---|---|---|
| Estructura | Lista ordenada (Índice 0, 1, 2...) | Diccionario (Clave -> Valor) |
| Acceso | Rápido por índice, lento por valor | Rápido por clave (Casi instantáneo) |
| Duplicados | Sí | Claves NO, Valores SÍ |
| Uso Ideal | Listados secuenciales, inventarios | Búsquedas por ID, Cachés, Índices |
Consejo Pro: Claves Inmutables
Intentad usar siempre objetos inmutables como claves (String, Integer, UUID).
¿Por qué? Si usas un objeto propio (Persona) como clave y luego le cambias el nombre a esa persona, su código hash cambia. ¡El HashMap ya no podrá encontrarlo y perderás el dato para siempre en el limbo de la memoria! 👻
Ejercicios Reflexivos
- Colisiones: ¿Qué pasa si dos objetos diferentes tienen el mismo
hashCode? ¿El HashMap explota o sabe manejarlo? (Investiga "Chaining" o encadenamiento). - Null Pointer: ¿Qué ocurre si intentas hacer
map.get("clave inexistente")y asignas el resultado directamente a unintprimitivo (noInteger)? - Orden: Ejecuta un
HashMapimprimiendo las claves. ¿Salen en el mismo orden en el que las insertaste? Si necesitas orden de inserción, ¿qué clase hermana de HashMap usarías?
Aplicación en el Mundo Real
- Caché de Datos:
Imagina que consultas una base de datos lenta para obtener el perfil de un usuario. Para no consultarla mil veces, guardas el resultado en un
HashMap<ID_Usuario, Perfil>. La próxima vez, miras primero en el mapa (RAM). - Sesiones Web:
En un servidor web, cada usuario tiene una sesión. El servidor usa un mapa gigante
HashMap<SessionID, DatosUsuario>para saber quién eres cada vez que haces clic en un enlace. - JSON:
Un objeto JSON (
{"nombre": "Pepe", "edad": 20}) es, estructuralmente, un Mapa de claves Strings a valores Object. Muchas librerías de Java parsean JSON directamente aHashMap.
Para Saber Más
-
Documentación Oficial de HashMap: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html
-
W3Schools - Java HashMap: Introducción sencilla y visual. https://www.w3schools.com/java/java_hashmap.asp
-
GeeksForGeeks - HashMap Examples: Casos de uso específicos. https://www.geeksforgeeks.org/java/hashmap-class-methods-java-examples-set-1-put-get-isempty-size/