Java 中的Map接口是集合框架中一个非常重要的组成部分,它用于存储键值对。从 JDK 1.7 到最新的长期支持版本 JDK 21,Map接口经历了一系列的演变和发展,引入了许多新的方法和改进了已有功能,以更好地满足开发者的需要。下面我们将详细介绍这些变化。
JDK 1.7 中的 Map 接口
在 JDK 1.7 时期,Map接口已经相当成熟,提供了一套基础但强大的 API 来操作键值对数据结构。此时的Map接口主要包含了如下一些常用的方法:
- put(K key, V value): 向Map中添加键值对。
- get(Object key): 根据给定的键获取对应的值。
- remove(Object key): 移除指定键及其关联的值。
- containsKey(Object key): 判断此Map是否包含指定键。
- containsValue(Object value): 判断此Map是否包含指定值。
- size(): 返回Map中的键值对数量。
- isEmpty(): 测试Map是否为空。
- keySet()、values()、entrySet() 分别返回所有键、所有值以及所有键值对的集合视图。
此外,JDK 1.7 还特别为并发场景下的Map实现(即ConcurrentHashMap)做了一些优化,比如使用分段锁来提高多线程环境下的性能。
JDK 8 对 Map 得增强
到了 JDK 8,Map接口迎来了几个显著的变化。
(1) 新增方法
// 1. 获取指定键对应的值,如果键不存在,则返回一个默认值。
V getOrDefault(Object key, V defaultValue)
// 2. 使用函数接口遍历 Map中得键值对。
void forEach(BiConsumer<? super K, ? super V> action)
// 3. 使用函数接口替换 Map中得键值对。
void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
// 4. 只有在给定的键尚未与值关联时(或者关联值未 null),才会将指定的键值对插入到此Map中。
V putIfAbsent(K key, V value)
// 5. 用于根据给定的键(key)以及一个能根据键和当前值生成新值的函数(rf),
// 来对 Map 中的键值对进行动态计算和更新操作。
V compute(K key, BiFunction<? super K, ? super V, ? extends V> rf)
// 6. 只有在给定的键尚未与值关联时(或者关联值未 null),才会将指定的键值对插入到此Map中,
// 注意如果 mf 函数返回 null 则不会插入。
V computeIfAbsent(K key, Function<? super K, ? extends V> mf)
// 7. 用于在 Map 中处理键值对的合并操作,根据指定的键(key)、一个默认值(value)
// 以及一个用于合并计算的函数(rf)来决定如何更新或添加键值对。
V merge(K key, V value, BiFunction<? super K, ? super V, ? extends V> rf)
// 8. 下方两个方法是 Map.Entry中新增得两个静态方法,分别用于对Map的Entry集合进行排序操作,
// 排序得依据分别是 key 得自然序或者 value 得自然序。当然这两个方法也支持传入自定义排序函数。
static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey()
static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)
// 9. 仅当指定的键当前映射到指定的值时,才删除该键值对。
boolean remove(Object key, Object value)
// 10. 将指定键(key)在Map中所关联的值替换为新给定的值(value)。
V replace(K key, V value)
// 11. 用于在Map中替换与指定键(key)相关联的值。但替换操作是有条件的,
// 只有当传入 key 得映射值 与传入的要被替换的旧值 oldValue 参数完全匹配时,
// 才会将该键对应的的值替换为新的值 newValue。
V replace(K key, V oldValue, V newValue)
// 12. 对 Map 中的每一个键值对,根据给定的一个二元函数(function)来更新键值对的值部分。
V replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
(2) 并发优化
对于 Map 接口得并发实现类ConcurrentHashMap,JDK 1.8 对 ConcurrentHashMap 进行了重大修改,采用了 CAS(Compare - And - Swap)操作和 Synchronized 关键字来实现更细粒度的并发控制,摒弃了原来的分段锁机制并且修改了历程数据结构,可以实现更高效得读写操作。性能方面,当数据量较大且hash冲突比较严重时,查询效率也得到了显著提升。
JDK 9 至 JDK 21 中 Map 的发展
从 JDK 9 开始直到最新的 JDK 21,虽然没有像 JDK 8 那样大规模地扩展Map接口的功能,但是仍然有一些细微但实用的内部优化被引入进来。
(1) JDK 9 更新
JDK 9 为 Map 接口提供了一系列的静态工厂方法,如 of() 方法。这些方法可以更方便地创建不可变的 Map 实例
// 不可变Map创建
static <K,V> Map<K,V> of()
static <K,V> Map<K,V> of(K k1, V v1)
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2)
// ... 最多支持10个键值对
// 超过10个键值对使用
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
这种方式创建的Map是不可变的,任何尝试修改它的操作都会抛出 UnsupportedOperationException。它在需要创建固定内容的Map时非常有用,比如配置信息、常量Map等场景
(2) JDK 10 - JDK 21 的改动和新增方法(相对稳定阶段)
在这期间Map接口本身没有太多大规模的改动,但 Java 整体的性能优化和内部实现的调整可能会间接地影响Map的性能。例如 JDK 在垃圾回收机制、内存管理等方面的改进都会对存储在中的对象的生命周期和内存占用产生积极的影响。
JDK 10 新增了一个 copyOf() 方法,用于创建一个现有Map的不可变副本。
// 创建不可变副本
static <K,V> Map<K,V> copyOf(Map<? extends K,? extends V> map)
其他三方库
虽然 Map 接口在 JDK 10 之后再无大的变动,但是还有很多其他三方库实现了对 Map 得增强。
1.Google 的 Guava 库
(1) 创建方式的增强
多种类型 Map 创建和灵活的不可变 Map 创建,Maps.newHashMap()来创建类似java.util.HashMap的普通可变Map,如Maps.immutableMap()用来创建不可变Map。
(2) 强大的转换操作(键值对转换)、过滤操作(按键或值过滤)
Maps.transformEntries():该方法允许根据一个自定义的函数来转换Map中的每个键值对、Maps.filterKeys()和Maps.filterValues():这两个方法分别用于对Map的键和值进行过滤。
(3) 引入了能处理一对多关系的 Multimap(多值)
Guava 的Multimap是一种特殊的Map,它允许一个键对应多个值,解决了传统Map在处理一对多关系时的不便。这在很多实际场景中非常有用,比如一个学生可以选多门课程,一本书可以有多个作者等。
(4) 比较两个 Map 之间得差异
Maps.difference()方法用于比较两个Map之间的差异。它返回一个MapDifference对象,这个对象包含了丰富的信息,如只在左边Map出现的键值对、只在右边Map出现的键值对、在两个Map中都出现但值不同的键值对以及在两个Map中都出现且值相同的键值对等。这对于数据同步、版本比较等场景非常有帮助。
2.Apache 的 Commons Collections库
(1) 双向映射(BidiMap)
Commons Collections 中的 BidiMap 是一种特殊的 Map,它提供了双向查找的功能。传统的 Map 是通过键查找值,而 BidiMap 允许通过值反向查找键。这种双向映射在很多场景下非常有用,例如在用户认证系统中,可能需要通过用户 ID(键)查找用户详细信息(值),同时也需要通过用户的某些唯一标识(如用户名)来获取用户 ID。
(2) 多值映射(MultiValueMap)
MultiValueMap 用于处理一个键对应多个值的情况,类似于 Guava 中的 Multimap。它提供了方便的方法来添加、获取和操作多值映射。这种数据结构在处理具有一对多关系的数据时非常有用,比如在权限管理系统中,一个用户角色(键)可能对应多个权限(值)。
(3) 基于 Transformer 和 Closure 的操作
- Transformer(转换):可以用于将Map中的元素从一种形式转换为另一种形式。它接受一个输入对象,并返回转换后的对象。通过Transformer,可以对Map中的值进行各种数学运算、格式化等操作。例如,将Map中的所有整数值乘以一个固定的系数。
- Closure(闭包):用于对Map中的元素执行某种操作,如更新值等。它可以在不返回新对象的情况下直接修改Map中的元素,这在需要批量修改Map元素的场景下非常有用,比如将Map中所有字符串值转换为大写形式。
3.Eclipse 的Collections库
其实 Eclipse 基金会也创建一个开源的 Java 集合框架库叫 eclipse-collections。它具有优化的数据结构和丰富、实用且流畅的 API。
只是目前使用使用的人比较少,这里给大家简单介绍一下功能,有兴趣可以自行去 github 阅读官方说明。
github 地址:https://github.com/eclipse/eclipse-collections
- 对 Map 的主要增强如下:
- 创建和初始化可变与不可变 Map。
- 操作链支持增强: MutableMap 支持操作链,它可以在一个表达式中连续进行多个操作,如添加、删除和更新键值对。例如MutableMap<String, Integer> mutableMap = Maps.mutable.of("key1", 1, "key2", 2).put("key3", 3);
- 分组功能增强: 提供了强大的 GroupBy 操作,可以根据指定的规则对 Map 中的元素进行分组
- 视图和迭代增强: 可以创建只读视图。
- 与其他集合类型协同操作增强: 对于 Map 来说,它可以方便装换成其他集合(如 List、Set 等)。
总结
本文给大家介绍了关于 Java 中 Map 接口在 JDK1.7 到 JDk21 中演进以及新增方法,还介绍了一些 Guava、Commons Collections、Eclipse Collections 对 Map 的增强,希望大家喜欢。