Map接口详解


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());
         }
     }

 

 

抽象方法

序号方法说明
1void clear()清除 Map 中所有数据
2boolean containsKey(Object key)判断 Map 中是否有该键
3boolean containsValue(Object value)判断 Map 中是否有该值
4Set<Map.Entry<K, V>> entrySet()将每一对键值对通过 Set 存放
5boolean equals(Object o)判断与另一个对象是否相等
6V get(Object key)根据键获取对应的值
7int hashCode()计算对象的 hashCode
8boolean isEmpty()判断 Map 是否存在并且是否为空
9Set<K> keySet()将所有的键存入一个 Set 中
10V put(K key, V value)往 Map 中放入键值对
11void putAll(Map<? extends K, ? extends V> m)往 Map 中放入另一个 Map
12V remove(Object key)通过键移除一个键值对
13int size()返回 Map 中存储键值对的数量
14Collection<V> values()获取 Map 中所有的值,存储在一个 Collection 集合中

 

默认实现方法

JDK1.8 中在 Map 中默认实现了一些方法

序号方法说明
1default V getOrDefault(Object key, V defaultValue)通过键获取一个值,如果不存在设置默认值 defaultValue
2default void forEach(BiConsumer<? super K, ? super V> action)遍历 Map,通过 函数式接口的 lambda 表达式实现遍历操作:打印、求和等
3default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)替换所有的键值对
4default V putIfAbsent(K key, V value)如果不存在键值对就添加键值对,否则就不添加
5default boolean remove(Object key, Object value)移除键值对,键值对必须全部匹配,否则失败
6default boolean replace(K key, V oldValue, V newValue)类似于乐观锁,如果根据键值得到的键值和期望的旧值oldValue 相等,就替换为新值,否则失败
7default V replace(K key, V value)替换已经存在的键值对
8default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)如果对应键不存在键值对,则使用自定义 lambda [Function<K,V>]指定根据键返回值,并且存放入 Map;如果返回 null,就不放入 Map ,并且返回 null
9default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)如果对应键存在键值对,使用自定义 lambda [BiFunction<T,U,R>],根据key 和 U 生成 对应值,并将键值对放入 Map;如果不存在键值对,返回null;如果生成对应值为 null ,会将 key 对应键值对删除
10default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)根据键(key)获取旧值,自定义 lambda [BiFunction<T,U,R>] ,通过 key 和 旧值 生成新值,将新键值对放入Map;如果生成新值为 null,并且旧值存在则删除旧的键值对,旧的键值对不存在就返回 null
11default 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 中接口 可以有 实现方法:

  1. static 的修饰方法

  2. 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();
     }
 /**
 输出:
     我是男人
 */

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值