Map 存储键值对
HashMap(来自 java.util)存储的是对应关系:每个键映射到一个值,你可以按键查找值,时间大致是常数级。可以把它想成一本词典:单词是键,释义是值。
两个类型参数是 <KeyType, ValueType>。这里键是 String,值是 Integer。和 ArrayList 一样,通常把变量声明为 Map 接口类型,再构造一个 HashMap。
put、get 与覆盖
有两点要牢记:
- 键是唯一的。对已存在的键调用
put会替换它的值,并返回旧值。 - 对不存在的键调用
get会返回null,而不是报错。把这个null自动拆箱成int会抛出NullPointerException,这是常见的 bug 来源。
用 getOrDefault 避开 null 陷阱
与其每次都检查 null,不如直接要一个默认值:
这是处理“可能存在”的查找最干净的方式,它也直接引出了 HashMap 最有名的用法。
统计出现次数
统计每个元素出现了多少次,是 HashMap 的教科书式任务:
map.put(key, map.getOrDefault(key, 0) + 1) 这个模式的意思是“取出当前计数(没有就是零),加一,再存回去”。更简洁的等价写法是 counts.merge(word, 1, Integer::sum)。
检查与删除
putIfAbsent(key, value) 只在键不存在时才写入,适合用来做延迟初始化。
遍历 HashMap
最常见的循环是遍历 entrySet(),同时拿到每个键和值:
如果只需要键,或者只需要值:
你也可以把 forEach 和 lambda 搭配使用:ages.forEach((name, age) -> System.out.println(name + ": " + age));。
HashMap 不保证顺序
HashMap 对遍历顺序不做任何保证:顺序取决于哈希的结果,而且每次运行都可能不同。如果你需要可预测的顺序:
LinkedHashMap保留插入顺序。TreeMap让键按自然顺序(或你提供的Comparator)排序。
三者都实现了 Map 接口,所以切换只需改一行构造函数。
键必须可哈希
HashMap 通过对键做哈希来查找条目,所以键的 hashCode() 和 equals() 必须保持一致。String 和 Integer 等内置类型已经正确实现了这一点。如果你用自己的类作为键,就要同时重写 equals 和 hashCode,否则两个在含义上“相等”的对象会落进不同的桶里,查找会莫名其妙地失败。
下一篇:HashSet
HashMap 回答的是“这个键下面存的是什么值?”。如果你只关心某个东西在不在,也就是一组没有附带数据的唯一值,那么要用的工具是 HashSet,下一篇就讲它。
常见问题
Java 中如何创建 HashMap?
用两个类型参数(键的类型和值的类型)声明它,然后调用构造函数:Map<String, Integer> ages = new HashMap<>();。接着用 ages.put("Ada", 36); 添加条目,用 ages.get("Ada"); 读取。需要导入 java.util.HashMap 和 java.util.Map。
Java 中如何遍历 HashMap?
用 for-each 循环遍历 map.entrySet(),就能同时拿到每个键和值:for (Map.Entry<String, Integer> e : map.entrySet()) { ... },再读取 e.getKey() 和 e.getValue()。也可以遍历 map.keySet() 只取键,或遍历 map.values() 只取值。注意 HashMap 不保留插入顺序。
get 和 getOrDefault 有什么区别?
get(key) 返回键对应的值,如果键不存在则返回 null,直接使用这个结果可能导致 NullPointerException。getOrDefault(key, fallback) 在值存在时返回值,否则返回你传入的默认值,这样就省掉了 null 检查。它在计数时特别方便:counts.put(c, counts.getOrDefault(c, 0) + 1)。