在Java开发中,HashMap是最常用的键值对存储结构之一。当我们需要把Map中所有的键名都取出来,并且根据每一个键名去提取它对应的数值时,使用keySet方法是一种非常直接且易于理解的做法。keySet会返回当前HashMap中所有键组成的Set集合,我们可以对这个Set进行遍历,在循环体里通过get方法拿到键对应的value。

一、keySet方法的基本用法
HashMap的keySet方法定义在Map接口中,它返回的是一个Set<K>视图。这个视图和原Map是绑定的,也就是说如果后续Map发生了结构修改,keySet看到的集合内容也会随之变化。我们在遍历这个Set时,每得到一个键对象,就可以调用HashMap的get方法,把键作为参数传入,从而取得映射的数值。
下面是一段最基础的示例代码,展示了如何声明一个HashMap,存入若干键值对,然后使用keySet遍历所有键名并提取数值:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class KeySetDemo {
public static void main(String[] args) {
// 创建HashMap并放入整数键和字符串数值
Map<Integer, String> map = new HashMap<>();
map.put(1, "苹果");
map.put(2, "香蕉");
map.put(3, "橙子");
// 通过keySet获取所有键的Set视图
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
// 根据键名提取对应的数值
String value = map.get(key);
System.out.println("键: " + key + ", 值: " + value);
}
}
}
上面的代码首先构造了一个键为Integer、值为String的HashMap,随后调用keySet得到所有键的集合,利用增强for循环逐个取出键,并通过map.get(key)提取数值。这种方式逻辑清楚,适合刚接触集合框架的开发者快速上手。
需要注意的是,keySet返回的Set本身不支持add操作,如果尝试向其中添加元素会抛出UnsupportedOperationException。它只是原Map的一个视图,所有对键的遍历实质上还是在访问HashMap内部的数据。
二、使用Iterator避免并发修改异常
如果在遍历keySet的同时,直接通过HashMap的put或remove方法修改集合结构,就会触发ConcurrentModificationException。为了避免这个问题,可以使用Iterator进行遍历,并在需要删除元素时调用Iterator自身的remove方法。
以下示例演示了如何在遍历过程中安全地移除某些键值对:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class KeySetIteratorDemo {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("张三", 90);
scores.put("李四", 58);
scores.put("王五", 77);
Set<String> keys = scores.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String name = it.next();
Integer score = scores.get(name);
// 如果分数低于60,从集合中移除
if (score != null && score < 60) {
it.remove();
}
}
System.out.println("及格名单: " + scores);
}
}
在这段代码中,我们使用keySet拿到键集合后获取了Iterator对象。循环里先通过next方法拿到键名,再用get提取数值做判断。当发现数值不达标时,调用it.remove而不是scores.remove,这样就避免了并发修改异常。
Iterator方式在JDK较早版本中尤为常见,即便在今日,若需要在遍历时删除元素,依然是最稳妥的选择。相比直接在for-each里调用map.remove,它的语义更明确,也更符合集合框架的设计规范。
三、提取数值时的空值处理
根据键名提取数值时,如果Map中并不包含该键,get方法会返回null。当数值类型是基本类型包装类(如Integer、Double)时,直接拆箱可能导致NullPointerException。因此在用keySet遍历并提取数值的过程中,应当先做空值判断,或者借助containsKey方法确认键存在。
下面的例子展示了一种安全的提取方式:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class SafeGetDemo {
public static void main(String[] args) {
Map<String, Double> prices = new HashMap<>();
prices.put("笔", 3.5);
prices.put("本", 5.0);
Set<String> productKeys = prices.keySet();
for (String product : productKeys) {
// 先判断键是否存在,再提取数值
if (prices.containsKey(product)) {
Double price = prices.get(product);
if (price != null) {
System.out.println(product + " 价格: " + price);
}
}
}
}
}
示例里通过containsKey和null判断双重保护,确保提取出来的数值可以安全使用。虽然HashMap的get在键存在时通常不会返回null(除非你真的存了null值),但良好的防御性编程能减少运行期错误。
另外,从Java 8开始,也可以考虑使用getOrDefault方法为可能缺失的键提供默认值,这样在遍历keySet提取数值时代码会更简洁,也免去了显式的空值判断分支。
四、keySet遍历与entrySet的对比
很多资料会提到,遍历HashMap时entrySet比keySet更高效,因为在entrySet中可以直接拿到键值对,而不必再调用一次get。确实,在只需要键或只在少数情况下用值时,keySet更直观;但如果对每个键都要提取数值,entrySet能省去一次哈希查找。
我们可以通过简单对比来看差异:
| 遍历方式 | 适用场景 | 性能特点 |
|---|---|---|
| keySet + get | 只需键名或偶尔取值 | 每次get有一次查找开销 |
| entrySet | 键和值都要频繁使用 | 直接拿值,无额外查找 |
尽管entrySet在纯性能上占优,但使用keySet根据键名提取数值依然是合法且易读的做法。在代码评审中,如果遍历逻辑简单、数据量不大,keySet写法反而让后来维护的人一眼看懂意图。
总结来说,使用HashMap的keySet遍历所有键名并根据键名提取数值,核心就是获取键的Set视图、循环取出键、用get取值,并处理好空值和并发修改的问题。理解它和entrySet的差异,能帮助我们在不同业务场景下写出更合适的代码。