Java 集合学习笔记:Map
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:Map相关的知识,希望对你有一定的参考价值。
Java 集合学习笔记:Map
- UML
- 简介
- 源码阅读
- 嵌套类
- 比较器
- 默认方法
- compute(K key, BiFunction<K, V, V> fun)
- computeIfAbsent(K key, Function<K, V> fun)
- computeIfPresent(K key, BiFunction<K, V, V> fun)
- forEach(BiConsumer<K, V> action)
- getOrDefault(Object key, V defaultValue)
- merge(K key, V value, BiFunction<V, V, V> fun)
- putIfAbsent(K key, V value)
- remove(Object key, Object value)
- replace(K key, V value)
- replace(K key, V oldValue, V newValue)
- replaceAll(BiFunction<K, V, V> function)
- 参考资料
UML
简介
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
.
此接口取代Dictionary
类,后者完全是一个抽象类,而不是一个接口。
.
Map
接口提供三种collection
视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的collection
视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如TreeMap
类;另一些映射实现则不保证顺序,如HashMap
类。
.
注:将可变对象用作映射键时必须格外小心。当对象是映射中某个键时,如果以影响equals
比较的方式更改了对象的值,则映射的行为将是不确定的。此项禁止的一种特殊情况是不允许某个映射将自身作为一个键包含。虽然允许某个映射将自身作为值包含,但请格外小心:在这样的映射上equals
和hashCode
方法的定义将不再是明确的。
.
所有通用的映射实现类应该提供两个“标准的”构造方法:一个void
(无参数)构造方法,用于创建空映射;一个是带有单个Map
类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是JDK
中所有通用的映射实现都遵从它。
.
此接口中包含的“破坏”方法可修改其操作的映射,如果此映射不支持该操作,这些方法将抛出UnsupportedOperationException
。如果是这样,那么在调用对映射无效时,这些方法可以(但不要求)抛出UnsupportedOperationException
。例如,如果某个不可修改的映射(其映射关系是“重叠”的)为空,则对该映射调用putAll(Map)
方法时,可以(但不要求)抛出异常。
.
某些映射实现对可能包含的键和值有所限制。例如,某些实现禁止null
键和值,另一些则对其键的类型有限制。尝试插入不合格的键或值将抛出一个未经检查的异常,通常是NullPointerException
或ClassCastException
。试图查询是否存在不合格的键或值可能抛出异常,或者返回false
;某些实现将表现出前一种行为,而另一些则表现后一种。一般来说,试图对不合格的键或值执行操作且该操作的完成不会导致不合格的元素被插入映射中时,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此接口的规范中标记为“可选”。
.
此接口是Java Collections Framework
的成员。
.
Collections Framework
接口中的很多方法是根据equals
方法定义的。例如,containsKey(Object key)
方法的规范中写道:“当且仅当此映射包含针对满足(key==null ? k==null : key.equals(k))
的键 k 的映射关系时,返回true
”。不 应将此规范解释为:调用具有非空参数key
的Map.containsKey
将导致对任意的键k
调用key.equals(k)
。实现可随意进行优化,以避免调用 equals,例如,可首先比较两个键的哈希码(Object.hashCode()
规范保证哈希码不相等的两个对象不会相等)。一般来说,只要实现者认为合适,各种Collections Framework
接口的实现可随意利用底层Object
方法的指定行为。
源码阅读
嵌套类
interface Entry<K,V>
https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html
Map
中存放的是 Entry
,Entry
中再存放 Key、Value
在 HashMap
中有实现 static class Node<K,V> implements Map.Entry<K,V>
详情看 HashMap 学习笔记。
静态方法
访问修饰符&返回类型 | 方法 | 描述 |
---|---|---|
static <K extends Comparable<? super K>,V> Comparator<Map.Entry<K,V>> | comparingByKey() | 返回一个比较器。按 key 升序排列 Map.Entry |
static <K,V> Comparator<Map.Entry<K,V>> | comparingByKey(Comparator<? super K> cmp) | 返回一个比较器,使用给定的 key 比较 Map.Entry 。 |
static <K,V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> | comparingByValue() | 返回一个比较器。按 value 升序排列 Map.Entry |
static <K,V> Comparator<Map.Entry<K,V>> | comparingByValue(Comparator<? super V> cmp) | 返回一个比较器,使用给定的 value 比较 Map.Entry 。 |
boolean | equals(Object o) | 比较指定对象与此项的相等性。 |
K | getKey() | 返回与此项对应的键。 |
V | getValue() | 返回与此项对应的值。 |
int | hashCode() | 返回此映射项的哈希码值。 |
V | setValue(V value) | 用指定的值替换与此项对应的值(可选操作)。 |
comparingByKey()
默认使用 key
按 自然升序
比较。返回的比较器,被强转为可序列化的。
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey()
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
comparingByKey(Comparator<? super K> cmp)
使用 key
按给定的比较规则 cmp
比较。返回的比较器,被强转为可序列化的。
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp)
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
comparingByValue()
默认使用 vale
按 自然升序
比较。返回的比较器,被强转为可序列化的。
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue()
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
comparingByValue(Comparator<? super V> cmp)
使用 value
按给定的比较规则 cmp
比较。返回的比较器,被强转为可序列化的。
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
比较器
Interface Comparable< T >
https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html
访问修饰符&返回类型 | 方法 | 描述 |
---|---|---|
int | compareTo(T o) | 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。 |
默认方法
访问修饰符&返回类型 | 方法 | 描述 |
---|---|---|
void | clear() | 从此映射中移除所有映射关系(可选操作)。 |
default V | compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) | 对给定 K 的值 使用映射函数mappingFunction 进行更新并返回新值。如果 新值 为 null ,执行 remove(key) 并返回 null |
default V | computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) | 如果值不存在 或为null ,使用 mappingFunction 的结果赋值,如果赋值成功,返回新值,否则返回 null 。(mappingFunction 结果为 null 时直接返 null ) |
default V | computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) | 如果值存在 且非null ,使用 mappingFunction 计算新值。如果新值非null 更新并返回新值,否则 remove(key) 并返回null 。 |
boolean | containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true。 |
boolean | containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true。 |
Set<Map.Entry<K,V>> | entrySet() | 返回此映射中包含的映射关系的 Set 视图。 |
boolean | equals(Object o) | 比较指定的对象与此映射是否相等。 |
default void | forEach(BiConsumer<? super K,? super V> action) | 两个入参的消费者,默认按 entrySet 顺序逐个消费。 |
V | get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 |
default V | getOrDefault(Object key, V defaultValue) | 如果 key 找不到映射的 value (null也算有 )。则返回给定的默认值 。 |
int | hashCode() | 返回此映射的哈希码值。 |
boolean | isEmpty() | 如果此映射未包含键-值映射关系,则返回 true。 |
Set | keySet() | 返回此映射中包含的键的 Set 视图。 |
default V | merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) | 值不存在 或为null 时用 value 赋值,否则用 mappingFunction 更新。如 mappingFunction 返回null 等同于remove 。 |
V | put(K key, V value) | 将指定的值与此映射中的指定键关联(可选操作)。 |
void | putAll(Map<? extends K,? extends V> m) | 从指定映射中将所有映射关系复制到此映射中(可选操作)。 |
default V | putIfAbsent(K key, V value) | 如果值存在 且非空 执行 get(k) ,否则执行put(k, v) 并返回其结果。 |
V | remove(Object key) | 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 |
default boolean | remove(Object key, Object value) | 键、值 都匹配则删除。 |
default V | replace(K key, V value) | 有对应值就执行替换。 |
default boolean | replace(K key, V oldValue, V newValue) | 如果key 确实映射到 oldValue 则替换值为newValue 。 |
default void | replaceAll(BiFunction<? super K,? super V,? extends V> function) | 调用给定函数BiFun ,遍历替entry 用BiFun 返回值替换元素的值,直到完成所有或抛异常。 |
int | size() | 返回此映射中的键-值映射关系数。 |
Collection | values() | 返回此映射中包含的值的 Collection 视图。 |
Java8 开始引入了默认方法
概念,允许向接口中添加默认实现。(用来兼容升级接口)
以下都默认方法。
compute(K key, BiFunction<K, V, V> fun)
- test
@Test
public void computeTest()
Map<Object, Object> map = new WeakHashMap<>();
map.put("a", 1);
Object result = map.compute("a", (k, v) -> k + " : " + v);
System.out.println("原值:" + result); // 1
System.out.println("更新后:" +map); // a=a : 1
result = map.compute("b", (k, v) -> null);
System.out.println("原值:" + result); // null
System.out.println("更新后:" +map); // a=a : 1
result = map.compute("a", (k, v) -> null);
System.out.println("原值:" + result); // null
System.out.println("更新后:" +map); //
compute
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
// 检测给定的 lambda 方法 remappingFunction 如果为 null 就抛锅。
Objects.requireNonNull(remappingFunction);
// 先取出旧值缓存。
V oldValue = get(key);
// 对旧值应用给定的 lambda 方法算出新值
V newValue = remappingFunction.apply(key, oldValue);
// 如果新值为空,则移除原键值对,返回 null
// 否则新值不为空,则:用新值替换旧值,并返回新值。
if (newValue == null)
// 新值为空则,返回 null
// 但如果原 key 或 value 存在,则需要先删除原键值对,再返回 null
if (oldValue != null || containsKey(key))
remove(key);
return null;
else
return null;
else
put(key, newValue);
return newValue;
按理说,新值是否为 null
触发删除逻辑这段不是应该过样写么
// 不为空,替换,并返回新值
if (newValue != null)
put(key, newValue);
return newValue;
// 否则新值为空,key存在或旧值不为空,执行删除。
if (oldValue != null || containsKey(key))
remove(key);
// 最后返回 null
return null;
computeIfAbsent(K key, Function<K, V> fun)
- test
这时我们使用 WeakHashMap 因为它没有实现 computeIfAbsent 会直接用 Map 接口的默认方法
@Test
public void computeIfAbsentTest()
Map<Object, Object> map = new WeakHashMap<>();
map.put("a", 1);
Object a = map.computeIfAbsent("a", key -> DateUtil.today());
Object b = map.computeIfAbsent("b", key -> DateUtil.today());
Object c = map.computeIfAbsent("c", key -> null);
System.out.println(a); // 1
System.out.println(b); // 2022-11-16
System.out.println(c); // null
System.out.println(map); // a=1, b=2022-11-16
computeIfAbsent
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
// 检测给定的 lambda 方法 remappingFunction 如果为 null 就抛锅。
Objects.requireNonNull(mappingFunction);
// 声明返回变量
V v;
// 获取 key 对应的值,如果值存在,直接返回
// 否则 mappingFunction 生成一个新值填充。并返回新值
// 如果生成的也是 null 直接返回 null
if ((v = get(key)) == null)
V newValue;
if ((newValue = mappingFunction.apply(key)) != null)
put(key, newValue);
return newValue;
return v;
computeIfPresent(K key, BiFunction<K, V, V> fun)
- test
@Test
public void computeIfPresentTest()
Map<String, Integer> map = new WeakHashMap<>();
map.put("a", 1);
map.put("b", null);
// 值不为空,替换新值
Integer result = map.computeIfPresent("a", (k, v) -> 2 );
System.out.println("原值:" + result); // 2
System.out.println("更新后:" +map); // a=2, b=null
// 值为 null,啥也不做,直接返回 null
result = map.computeIfPresent("b", (k, v) -> 666 );
System.out.println("原值:" + result); // null
System.out.println("更新后:" +map); // a=2, b=null
// 按 key 找到值,执行 lambda 得到新值为空,移除键值对
result = map.computeIfPresent("a", (k, v) -> null );
System.out.println("原值:" + result); // null
System.out.println("更新后:" +map); // b=null
computeIfPresent
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
// 非空检测,如果为 null 就抛锅。
Objects.requireNonNull(remappingFunction);
// 声明临时变量用于存放旧值
V oldValue;
// 按 key 获取对应值,并付给 oldValue,如果为空直接返回 null
// 否则使用给定的 remappingFunction 处理原键值对,得出新值 newValue
// 新值不为空:调用 put 修改为 newValue。并返回 newValue
// 否则移除 键值对。返回 null
if ((oldValue = get(key)) != null)
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null)
put(key, newValue);
return newValue;
else
remove(key);
return null;
else
return null;
forEach(BiConsumer<K, V> action)
- test
@Test
public void forEachTest()
Map<String, Integer> map = new WeakHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.forEach((k,v) ->
System.out.println( "键="+ k + "; 值=" + v)Java学习笔记之:Java Map集合