Skip to content

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, devuelve null.
  • 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). Devuelve Object.
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 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

  1. Colisiones: ¿Qué pasa si dos objetos diferentes tienen el mismo hashCode? ¿El HashMap explota o sabe manejarlo? (Investiga "Chaining" o encadenamiento).
  2. Null Pointer: ¿Qué ocurre si intentas hacer map.get("clave inexistente") y asignas el resultado directamente a un int primitivo (no Integer)?
  3. Orden: Ejecuta un HashMap imprimiendo 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

  1. 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).
  2. 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.
  3. 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 a HashMap.

Para Saber Más