Jakob Jenkov 2020-09-21
Java 的 Map 接口(java.util.Map)表示键(key)与值(value)之间的映射关系。更具体地说,Java 的 Map 可以存储键值对(key-value pairs),每个键都关联一个特定的值。一旦将键值对存入 Map,之后就可以仅通过键来查找对应的值。
需要注意的是,Java 的 Map 接口 不是 Collection 接口的子类型,因此其行为与其他集合类型略有不同。
Java Map 的实现类
由于 Map 是一个接口,要使用它,必须实例化一个具体的实现类。Java 集合 API 提供了以下 Map 实现:
java.util.HashMapjava.util.Hashtablejava.util.EnumMapjava.util.IdentityHashMapjava.util.LinkedHashMapjava.util.Propertiesjava.util.TreeMapjava.util.WeakHashMap
根据我的经验,最常用的 Map 实现是 HashMap 和 TreeMap。
这些 Map 实现在迭代元素时的顺序以及插入和访问元素的时间复杂度(大 O 表示法)方面略有不同:
HashMap:将键映射到值,但不保证内部元素的顺序。TreeMap:也映射键和值,但它能保证迭代键或值时的顺序——即按键或值的排序顺序。
通常,HashMap 是两者中速度更快的实现。因此,只要不需要对 Map 中的元素进行排序,就应优先使用 HashMap;否则使用 TreeMap。
创建 Map
要创建一个 Java Map,必须实例化一个实现了 Map 接口的类。例如:
Map mapA = new HashMap();
Map mapB = new TreeMap();
泛型 Map(Generic Java Map)
默认情况下,你可以将任意 Object 放入 Map。但从 Java 5 开始,泛型(Generics) 允许你限制 Map 中键和值的类型。例如:
Map<String, MyObject> map = new HashMap<String, MyObject>();
这个 Map 现在只接受 String 类型的键和 MyObject 类型的值。这样,在访问和遍历时就不需要强制类型转换:
for (MyObject anObject : map.values()) {
// 对 anObject 执行操作...
}
for (String key : map.keySet()) {
MyObject value = map.get(key);
// 对 value 执行操作...
}
如果已知 Map 中存储的对象类型,建议在声明和创建 Map 时始终指定泛型类型。这有助于避免插入错误类型的对象,并让阅读代码的人更容易理解 Map 的内容。
向 Java Map 中插入元素
要向 Map 添加元素,调用其 put() 方法。例如:
Map<String, String> map = new HashMap<>();
map.put("key1", "element 1");
map.put("key2", "element 2");
map.put("key3", "element 3");
这三次 put() 调用将字符串值映射到字符串键。之后可以通过键获取对应的值(见下文)。
只能插入对象(Only Objects Can Be Inserted)
只有 Java 对象才能作为键或值存入 Map。如果传入基本类型(如 int、double 等),Java 会自动装箱(auto-boxing)为对应的包装类。例如:
map.put("key", 123);
这里的 123 是 int 基本类型,但会被自动装箱为 Integer 对象,因为 put() 方法要求参数为 Object 类型。
使用相同键的后续插入(Subsequent Inserts With Same Key)
一个键在 Map 中只能出现一次。也就是说,同一个键不能对应多个值。如果你多次调用 put() 并使用相同的键,最后一次传入的值会覆盖之前的值。
null 键(Null Keys)
令人惊讶的是,Java Map 允许使用 null 作为键:
Map map = new HashMap();
map.put(null, "value for null key");
要获取 null 键对应的值,只需将 null 作为参数传给 get() 方法:
Map<String, String> map = new HashMap<>();
String value = map.get(null);
null 值(Null Values)
Map 中的值也可以为 null:
map.put("D", null);
之后调用 get("D") 将返回 null。
从另一个 Map 插入所有元素
Map 接口提供了 putAll() 方法,可以将另一个 Map 中的所有键值对复制到当前 Map 中(集合论中称为“并集”):
Map<String, String> mapA = new HashMap<>();
mapA.put("key1", "value1");
mapA.put("key2", "value2");
Map<String, String> mapB = new HashMap<>();
mapB.putAll(mapA);
执行后,mapB 将包含 mapA 中的所有条目。注意:putAll() 是单向复制。
从 Java Map 中获取元素
使用 get() 方法并通过键来获取值:
Map<String, String> map = new HashMap<>();
map.put("key1", "value 1");
String element1 = map.get("key1"); // 无需强制转换
获取值或默认值(Get or Default Value)
Map 提供 getOrDefault() 方法,当键不存在时返回指定的默认值:
Map<String, String> map = new HashMap<>();
map.put("A", "1");
map.put("B", "2");
map.put("C", "3");
String value = map.getOrDefault("E", "default value"); // 返回 "default value"
检查 Map 是否包含某个键或值
- 检查键:
map.containsKey("123") - 检查值:
map.containsValue("value 1")
遍历 Java Map 的键
有三种常用方式:
1. 使用键的 Iterator
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = map.get(key);
}
2. 使用 for-each 循环
for (String key : map.keySet()) {
String value = map.get(key);
}
3. 使用 Stream(Java 8+)
map.keySet().stream().forEach(key -> {
System.out.println(key + " -> " + map.get(key));
});
遍历 Java Map 的值
通过 values() 方法获取值的集合:
1. 使用值的 Iterator
Iterator<String> iterator = map.values().iterator();
while (iterator.hasNext()) {
String value = iterator.next();
}
2. 使用 for-each 循环
for (String value : map.values()) {
System.out.println(value);
}
3. 使用 Stream
map.values().stream().forEach(System.out::println);
遍历 Java Map 的条目(Entry)
条目(Entry)即键值对。可通过 entrySet() 获取:
1. 使用 Entry Iterator
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
}
2. 使用 for-each 循环
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
}
从 Java Map 中删除条目
- 删除指定键:
map.remove("key1"); - 清空所有条目:
map.clear();
替换 Map 中的条目
replace() 方法仅在键已存在时才替换值(与 put() 不同):
map.put("key", "val1");
map.replace("key", "val2"); // 成功替换
map.replace("nonexistent", "val3"); // 无效果
获取 Map 的大小和判断是否为空
- 条目数量:
int size = map.size(); - 是否为空:
boolean empty = map.isEmpty();
Java Map 的函数式操作(Java 8+)
compute()
map.compute("123", (key, value) ->
value == null ? null : value.toString().toUpperCase()
);
- 若 lambda 返回
null,则删除该条目。
computeIfAbsent()
仅当键不存在时才计算并插入值:
map.computeIfAbsent("123", key -> "abc");
computeIfPresent()
仅当键存在时才更新值:
map.computeIfPresent("123", (key, value) ->
value.toString().toUpperCase()
);
merge()
合并新旧值:
map.merge("123", "XYZ", (oldValue, newValue) -> oldValue + "-" + newValue);
- 若键不存在,则插入
"XYZ"; - 若存在,则合并为
"oldValue-XYZ"。
更多信息
有关 Map 的更多功能,请查阅 Java 官方文档。
本文重点介绍了最常用的两个操作:增删元素 和 遍历键/值/条目。