HashMap en Java: guardar, buscar, contar y recorrer

Un HashMap guarda parejas de clave y valor y encuentra un valor por su clave al instante, tenga diez elementos o diez millones. Es probablemente la estructura de datos más usada de Java después de la lista. Aquí están las operaciones del día a día y los métodos modernos que ahorran casi todos los if.

Lo básico

import java.util.HashMap;
import java.util.Map;

void main() {
    Map<String, Integer> precios = new HashMap<>();
    precios.put("café", 35);
    precios.put("té", 28);
    precios.put("café", 38);                     // la misma clave: sustituye el valor

    IO.println(precios.get("café"));
    IO.println(precios.get("chocolate"));        // no existe: null
    IO.println(precios.getOrDefault("chocolate", 0));
    IO.println(precios.containsKey("té") + " " + precios.size());
    precios.remove("té");
    IO.println(precios);
}
salida38
null
0
true 2
{café=38}

get devuelve null si la clave no existe, y un null que se cuela en una suma da un NullPointerException. getOrDefault lo evita.

Contar y agrupar sin if

import java.util.*;

void main() {
    String texto = "el perro y el gato y el loro";
    Map<String, Integer> cuenta = new TreeMap<>();
    for (String palabra : texto.split(" ")) cuenta.merge(palabra, 1, Integer::sum);
    IO.println(cuenta);

    List<String> alumnos = List.of("Ana:3A", "Luis:3B", "Sofía:3A", "Diego:3C", "Iker:3B");
    Map<String, List<String>> porGrupo = new TreeMap<>();
    for (String a : alumnos) {
        String[] partes = a.split(":");
        porGrupo.computeIfAbsent(partes[1], g -> new ArrayList<>()).add(partes[0]);
    }
    IO.println(porGrupo);
}
salida{el=3, gato=1, loro=1, perro=1, y=2}
{3A=[Ana, Sofía], 3B=[Luis, Iker], 3C=[Diego]}
  • merge(clave, 1, Integer::sum): si la clave no está, guarda 1; si está, suma 1 a lo que había. Es el contador en una línea.
  • computeIfAbsent(clave, crear): devuelve el valor de la clave y, si no existe, lo crea antes. Para agrupar en listas es perfecto.

Recorrer y el orden

import java.util.*;

void main() {
    String[] meses = {"marzo", "enero", "diciembre", "abril"};
    Map<String, Integer> enOrdenDeLlegada = new LinkedHashMap<>();
    Map<String, Integer> ordenado = new TreeMap<>();
    for (String m : meses) { enOrdenDeLlegada.put(m, m.length()); ordenado.put(m, m.length()); }

    IO.println("LinkedHashMap: " + enOrdenDeLlegada.keySet());
    IO.println("TreeMap:       " + ordenado.keySet());

    for (Map.Entry<String, Integer> e : ordenado.entrySet()) {
        IO.println(e.getKey() + " tiene " + e.getValue() + " letras");
    }
}
salidaLinkedHashMap: [marzo, enero, diciembre, abril]
TreeMap:       [abril, diciembre, enero, marzo]
abril tiene 5 letras
diciembre tiene 9 letras
enero tiene 5 letras
marzo tiene 5 letras

Un HashMap no garantiza ningún orden al recorrerlo, y ese orden puede cambiar al añadir elementos: no dependas de él. Si te importa el orden, usa LinkedHashMap (el de inserción) o TreeMap (ordenado por la clave). Para recorrer, entrySet() da la clave y el valor a la vez, sin buscar cada clave otra vez.

Las claves de un HashMap necesitan equals y hashCode coherentes: con un String, un Integer, un enum o un record ya lo están. Con una clase tuya, sin esos dos métodos, dos objetos iguales cuentan como claves distintas.

Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.