Map<K,V>
是 Java 中特别常见的一种数据类型,以键值对存储数据。
日常开发中,最常用的几个Map:HashMap,ConcurrentHashMap 等
当然,一位合格的程序员都会深入学习,查看这些类的源码,首先我们要从源头开始看起,这些类都是 Map,我们先看看 Map 到底是个什么?
首先我们先看看 Map 族谱 【常用类】
Map族谱
这个图谱建议 自己手动 画一下,可以在 Idea 中光标移动到 Map 接口定义上 ctrl+h ,查看子类,找到这几个类,画出来。
这样你会更理解。
▌绿色:接口
浅蓝色:抽象类
▌蓝色:实现类
首先,我们看看整个继承图谱的源头:Map接口
Map <<interface>>
Map 是一个接口,定义了所有 Map 的规范,并且定义了一个内部接口:Entry<K,V> 用于存储数据
接口定义
我们先看看 Map 接口的定义
package java.util;
// Map 接口 位于包 java.util;
public interface Map<K,V> {}
内部接口 Entry<K,V>
方法 | 类型 | 说明 |
---|---|---|
K getKey() | abstract | 获得键 |
V getValue() | abstract | 获得值 |
V setValue(V value) | abstract | 设置值 |
boolean equals(Object o) | abstract | 判断与另一个对象是否相等 |
int hashCode() | abstract | 获取 Hash 值 |
<K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() | static | 根据键比较大小,要求键的类型继承 Comparable |
<K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() | static | 根据值比较大小,要求值的类型继承 Comparable |
<K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) | static | 自定义比较器,根据键比较大小 |
<K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) | static | 自定义比较器,根据值比较大小 |
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
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());
}
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());
}
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());
}
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());
}
}
抽象方法
序号 | 方法 | 说明 |
---|---|---|
1 | void clear() | 清除 Map 中所有数据 |
2 | boolean containsKey(Object key) | 判断 Map 中是否有该键 |
3 | boolean containsValue(Object value) | 判断 Map 中是否有该值 |
4 | Set<Map.Entry<K, V>> entrySet() | 将每一对键值对通过 Set 存放 |
5 | boolean equals(Object o) | 判断与另一个对象是否相等 |
6 | V get(Object key) | 根据键获取对应的值 |
7 | int hashCode() | 计算对象的 hashCode |
8 | boolean isEmpty() | 判断 Map 是否存在并且是否为空 |
9 | Set<K> keySet() | 将所有的键存入一个 Set 中 |
10 | V put(K key, V value) | 往 Map 中放入键值对 |
11 | void putAll(Map<? extends K, ? extends V> m) | 往 Map 中放入另一个 Map |
12 | V remove(Object key) | 通过键移除一个键值对 |
13 | int size() | 返回 Map 中存储键值对的数量 |
14 | Collection<V> values() | 获取 Map 中所有的值,存储在一个 Collection 集合中 |
默认实现方法
JDK1.8 中在 Map 中默认实现了一些方法
序号 | 方法 | 说明 |
---|---|---|
1 | default V getOrDefault(Object key, V defaultValue) | 通过键获取一个值,如果不存在设置默认值 defaultValue |
2 | default void forEach(BiConsumer<? super K, ? super V> action) | 遍历 Map,通过 函数式接口的 lambda 表达式实现遍历操作:打印、求和等 |
3 | default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | 替换所有的键值对 |
4 | default V putIfAbsent(K key, V value) | 如果不存在键值对就添加键值对,否则就不添加 |
5 | default boolean remove(Object key, Object value) | 移除键值对,键值对必须全部匹配,否则失败 |
6 | default boolean replace(K key, V oldValue, V newValue) | 类似于乐观锁,如果根据键值得到的键值和期望的旧值oldValue 相等,就替换为新值,否则失败 |
7 | default V replace(K key, V value) | 替换已经存在的键值对 |
8 | default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | 如果对应键不存在键值对,则使用自定义 lambda [Function<K,V>]指定根据键返回值,并且存放入 Map;如果返回 null,就不放入 Map ,并且返回 null |
9 | default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 如果对应键存在键值对,使用自定义 lambda [BiFunction<T,U,R>],根据key 和 U 生成 对应值,并将键值对放入 Map;如果不存在键值对,返回null;如果生成对应值为 null ,会将 key 对应键值对删除 |
10 | default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 根据键(key)获取旧值,自定义 lambda [BiFunction<T,U,R>] ,通过 key 和 旧值 生成新值,将新键值对放入Map;如果生成新值为 null,并且旧值存在则删除旧的键值对,旧的键值对不存在就返回 null |
11 | default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 如果存在旧的键值对,设置新值为新的键值对,并返回新值(value);如果九的键值对不存在,自定义 lambda [BiFunction<T,U,R>],根据旧值和 value 生成新值,新值不为 null 就将新的键值对放入Map,新的键值为 null 就删除旧的键值对 |
1. V getOrDefault (Object key, V defaultValue)
通过键获取一个值,如果不存在设置默认值 defaultValue
/**
* 通过键获取一个值,如果不存在设置默认值 defaultValue
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
2. void forEach(BiConsumer<? super K, ? super V> action)
遍历 Map,通过 函数式接口的 lambda 表达式实现遍历操作:打印、求和等
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
3. void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
替换所有的键值对
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
4. V putIfAbsent(K key, V value)
如果不存在键值对就添加键值对,否则就不添加
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
5. boolean remove(Object key, Object value)
移除键值对,键值对必须全部匹配,否则失败
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
6. boolean replace(K key, V oldValue, V newValue)
类似于乐观锁,如果根据键值得到的键值和期望的旧值
oldValue
相等,就替换为新值,否则失败
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
7. V replace(K key, V value)
替换已经存在的键值对
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
8. V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
如果对应键不存在键值对,则使用自定义 lambda [Function<K,V>]指定根据键返回值,并且存放入 Map;如果返回 null,就不放入 Map ,并且返回 null
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
9. V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
如果对应键存在键值对,使用自定义 lambda [BiFunction<T,U,R>],根据key 和 U 生成 对应值,并将键值对放入 Map;如果不存在键值对,返回null;如果生成对应值为 null ,会将 key 对应键值对删除
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
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;
}
}
10. V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
根据键(key)获取旧值,自定义 lambda [BiFunction<T,U,R>] ,通过 key 和 旧值 生成新值,将新键值对放入Map;如果生成新值为 null,并且旧值存在则删除旧的键值对,旧的键值对不存在就返回 null
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
11. V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
如果存在旧的键值对,设置新值为新的键值对,并返回新值(value);如果九的键值对不存在,自定义 lambda [BiFunction<T,U,R>],根据旧值和 value 生成新值,新值不为 null 就将新的键值对放入Map,新的键值为 null 就删除旧的键值对
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
思考
JDK 1.8 中接口 可以有 实现方法:
static 的修饰方法
default 修饰的方法
default 修饰的方法可以被继承和重写,static 修饰的方法不能被继承或重写
interface Show { public static void shoeInfo(){ System.out.println("static 你好"); } default void showInfo(){ System.out.println("default 你好"); } void aa(); } class AA implements Show { @Override public void aa() { } } class BB{ public static void main(String[] args) { Show show = new AA(); show.showInfo(); Show.shoeInfo(); } } /* 输出: default 你好 static 你好 */
▌为什么要有接口默认方法?
因为业务需要我们需要修改项目中的接口,在 JDK 1.8 之前,修改了接口必须一个一个实现类去修改,不然会产生编译错误,而某些实现类并不需要实现这个方法,但是还是要写一个空实现,很烦。
自从 JDK 1.8 后,接口中支持有 defult 默认方法后,
所以,接口默认方法就是为了解决这个问题,只要在一个接口添加了一个默认方法,所有的实现类就自动继承,不需要改动任何实现类,也不会影响业务,爽歪歪。
▌为什么要有接口静态方法?
接口静态方法和默认方法类似,只是接口静态方法不可以被接口实现类重写。
接口静态方法只可以直接通过静态方法所在的 接口名.静态方法名 来调用。
▌ 默认方法冲突问题 -> 继承的多个接口中有相同的默认方法
因为接口默认方法可以被继承并重写,如果继承的多个接口都存在相同的默认方法,那就存在冲突问题。
结构示例
创建两个接口 Human Man
Human
// 人类接口
interface HuMan{
default void show(){
System.out.println("我是人");
}
}
Man
// 男性接口
interface Man{
default void show(){
System.out.println("我是女人");
}
}
此时我们创建 年青男人 接口 YoungMan
interface YoungMan extends Man,Human{}
可以看到编译器爆红了,如果编译会出现编译异常
Error:(20, 5) java: 接口 cn.dcpnet.annotation.Wow.RenYao从类型 cn.dcpnet.test.Man 和 cn.dcpnet.test.Human 中继承了show() 的不相关默认值
因为 ReYao 继承了 Man 和 Woman 的 show() 方法,两个show 方法又不同,编译器并不知道继承哪一个,此时就需要在 RenYao 类中重写 show() 方法。
interface RenYao extends Man,Woman{
@Override
default void show() {
System.out.println("我是人妖");
}
}
▌ 默认方法冲突问题 -> 继承的多个接口中有相同的默认方法,多个接口中存在继承关系
结构示例
创建人类接口 Human
interface Human {
default void show(){
System.out.println("我是认类");
}
}
创建男人接口 Man
interface Man extends Human {
// 不写 Override 能通过编译,但是良好的编程规范应该写上
@Override
default void show(){
System.out.println("我是男人");
}
}
创建年轻男人接口 YoungMan
interface YoungMan extends Man,Human{}
此时并没有报错,那么,被调用的方法一定是被重写的 Man 的 show() 方法【常识就能判断出】,事实如此。
▌ 默认方法冲突问题 -> 父接口抽象方法被子类默认方法重写
结构示例
创建人类接口 Human
interface Human {
void show();
}
创建男人接口 Man
interface Man extends Human {
// 如果不写 @Overide 爆红,编译能通过, 默认被重写
default void show(){
System.out.println("我是男人");
}
}
创建实体类 YoungMan
class YoungMan implements Human,Man{}
测试
public static void main(String[] args) {
YoungMan youngMan = new YoungMan();
youngMan.show();
}
/**
输出:
我是男人
*/