Java 集合框架_Map

今天我们分析一下集合框架的另一套体系,键值对的Map集合。
Map集合是用键值对作为存储元素的集合,所以它可以通过key值来操作集合中的键值对元素。

一. Map接口

    public interface Map<K,V> {}

可以注意到Map接口是一个顶层接口,与Collection没有任何联系,甚至它都没有继承Iterable接口,说明Map是不可迭代的。但是Map集合提供了方法将集合中的元素转换成一个可迭代接口实例。

1.1 Map集合常用方法

那么Map接口应该提供哪些方法呢?

  1. 添加元素
    // 向集合中添加 键值对,但是如果key值存在,就替换对应的value值
    V put(K key, V value);

    // 将集合m中的键值对全部存放到本集合中
    void putAll(Map<? extends K, ? extends V> m);
  1. 删除元素
    // 根据键key移除Map集合中对应的键值对, 并返回value值
    V remove(Object key);
    // 清除集合中所有的键值对
    void clear();
  1. 替换元素
    // 向集合中添加 键值对,但是如果key值存在,就替换对应的value值
    V put(K key, V value);
  1. 查询方法
   // Map集合键值对中是否包括这个键key
    boolean containsKey(Object key);

    // Map集合键值对中是否包括这个value值
    boolean containsValue(Object value);

    // 返回Map集合中所有的键组成的Set集合。因为键是唯一的,所以是Set集合
    Set<K> keySet();

    // 返回Map集合中所有的值组成的Collection集合。因为值是可以重复的。
    Collection<V> values();

    // 返回Map集合中所有的键值对组成的Set集合。因为键值对也是唯一的。
    Set<Map.Entry<K, V>> entrySet();


    // 通过键key获取对应的值
    V get(Object key);
  1. 其他方法
    // Map集合元素数量
    int size();

    // 集合是否为空
    boolean isEmpty();

1.2 Map内部接口Entry

因为Map集合是用键值对储存元素的,所以它提供了键值对类的顶层接口Entry。它提供了三个重要方法

  • K getKey(); 获取key值
  • V getValue(); 获取value值
  • V setValue(V value); 替换value值
    Entry 提供了equals和hashCode方法,强制子类实现这个两个方法
        // 比较两个Entry是否相等的方法
        boolean equals(Object o);

        // 因为复写了equals方法,必须复写hashCode方法。来保证相同的Entry 它们的hashCode值必须也是相同的
        int hashCode();

它还提供四个静态方法,返回一个Comparator<Map.Entry<K,V>>比较器,用来比较两个Entry的大小的。

       // 工具类方法, 返回一个根据key值大小的比较器Comparator。
        // 调用Comparator的compare(T o1, T o2)方法,就可以比较两个Map.Entry大小,是根据Map.Entry的key值来判断的。
        // 所以必须保证key值的类型是实现Comparable接口,来确保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());
        }

        // 工具类方法,返回一个根据value值大小的比较器Comparator。
        // 所以必须保证value值的类型是实现Comparable接口,来确保value值是可以比较的
        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());
        }

        // 工具类方法,根据一个key值比较器Comparator<? super K> 生成一个 键值对比较器Comparator<Map.Entry<K, V>>
        // 很简单,因为键值对比较器Comparator的compare(T o1, T o2)方法 参数类型是Map.Entry,
        // 然后调用Map.Entry的getKey方法获取对应key值,然后调用key值比较器Comparator的compare方法就实现比较功能了。
        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());
        }

        // 工具类方法,根据一个value值比较器Comparator<? super V> 生成一个 键值对比较器Comparator<Map.Entry<K, V>>
        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.3 Map接口提供默认实现方法

因为Map集合是用键值对存储的,所以Map接口提供了一些根据key值操作集合的默认实现方法,主要是键值对的删除和替换方法。
再说这些方法之前,我们想一个问题,操作这些键值对,有两种方法,一种是根据key值(也就是key值在集合中存在),另一种是根据键值对(也就是说不仅key值在集合中存在,而且value值也要相等)

  • 怎么判断key值在集合中存在?

使用containsKey(key)方法返回true就行了。
但是有个问题,一般情况下我们都要得到key所对应的原value值,所以一般都是先调用V get(Object key)方法,然后再去调用containsKey(key)方法。
有人会说这不是傻么,不会先调用containsKey(key)方法,如果存在再调用get(Object key)方法么?这个就涉及到效率问题了,因为这两个方法都是遍历整个集合来查找对应的键值对,所以尽量不要同时使用这个两个方法。
这里就有一个小技巧了,get(Object key)返回值如果不是null,那么说明找到对应的键值对了,所以这个key值是包含的。
但是坑爹的是get(Object key)返回值是null,并不代表没有找到对应键值对,因为Map集合允许value值为null(甚至key值也允许为null)。所以还必须调用containsKey(key)方法进行二次循环遍历查找。
所以这个表示key值存在的判断式(((v = get(key)) != null) || containsKey(key))

  • 怎么判断键值对相等?

先通过get(Object key)查找curValue值,然后判断这个curValue值与传入的value是否相等,不相等就直接返回。相等的话,就判断key值在集合中是否存在。 一般判断条件如下:

         // 根据key值获取对应curValue值
        Object curValue = get(key);
        // 1. 如果值不相等,即!Objects.equals(curValue, value)为true,那么就直接返回false
        // 2. 如果值相等,判断key值在集合中是否存在。
        // (curValue == null && !containsKey(key)) 可以改写成
        // !(curValue != null || containsKey(key))
        if (!Objects.equals(curValue, value) ||
                (curValue == null && !containsKey(key))) {
            return false;
        }
package java.util;

import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.io.Serializable;

public interface Map<K,V> {

    // 添加元素
    // 向集合中添加 键值对,但是如果key值存在,就替换对应的value值
    V put(K key, V value);

    // 将集合m中的键值对全部存放到本集合中
    void putAll(Map<? extends K, ? extends V> m);


    // 删除元素
    // 根据键key移除Map集合中对应的键值对, 并返回value值
    V remove(Object key);

    // 移除Map集合中键值与key相等 v值与value相等的键值对(而不是只要求key相等)
    default boolean remove(Object key, Object value) {
        // 根据key值获取对应curValue值
        Object curValue = get(key);
        // 1. 如果值不相等,即!Objects.equals(curValue, value)为true,那么就直接返回false
        // 2. 如果值相等,判断key值在集合中是否存在。
        // (curValue == null && !containsKey(key)) 可以改写成
        // !(curValue != null || containsKey(key))
        if (!Objects.equals(curValue, value) ||
                (curValue == null && !containsKey(key))) {
            return false;
        }
        remove(key);
        return true;
    }

    // 清除集合中所有的键值对
    void clear();

    // 更新元素

    // 遍历集合中所有键值对元素, 通过BiFunction的apply方法,获取一个新的value值,然后替换键值对中的原来的value值
    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);
            }

            //  通过BiFunction的apply方法,获取一个新的value值,然后替换键值对中的原来的value值
            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);
            }
        }
    }

    // 如果key值对应的v值为null,就将这个键值对中的v替换成新的value值。
    // 如果key值对应的v值不为null,就不做替换操作
    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }

    // 找到与key、oldValue相等的键值对,然后将这个键值对中的value值替换成新newValue
    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;
    }

    // 找到与key值相等的键值对,然后将这个键值对中的curValue值替换成新value值,并返回原来的curValue值
    default V replace(K key, V value) {
        V curValue;
        if (((curValue = get(key)) != null) || containsKey(key)) {
            curValue = put(key, value);
        }
        return curValue;
    }

    // 如果Map集合不包含这个key值,或者key值对应的value为null,那么就调用mappingFunction的apply方法,
    // 生成新的newValue值。将这个newValue值和key值存放到Map集合中。
    default V computeIfAbsent(K key,
                              Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            // 注意 这里得到的新值不为null的时候,才添加到集合中
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }

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

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

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

    // 查询方法
    // Map集合键值对中是否包括这个键key
    boolean containsKey(Object key);

    // Map集合键值对中是否包括这个value值
    boolean containsValue(Object value);

    // 返回Map集合中所有的键组成的Set集合。因为键是唯一的,所以是Set集合
    Set<K> keySet();

    // 返回Map集合中所有的值组成的Collection集合。因为值是可以重复的。
    Collection<V> values();

    // 返回Map集合中所有的键值对组成的Set集合。因为键值对也是唯一的。
    Set<Map.Entry<K, V>> entrySet();


    // 通过键key获取对应的值
    V get(Object key);

    // 根据key值获取Map集合中对应value值,如果没有获取到,就返回默认值defaultValue
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        // 这里还要调用containsKey方法,这是因为Map集合允许存放value值为空的键值对
        // get(key)返回null,并不代表没有这个键值对(甚至key值都可以为null)。所以还要调用containsKey方法。
        return (((v = get(key)) != null) || containsKey(key))
                ? v
                : defaultValue;
    }


    // Map集合元素数量
    int size();

    // 集合是否为空
    boolean isEmpty();



    // 比较两个Map集合是否相等的方法
    boolean equals(Object o);

    // 因为复写了equals方法,必须复写hashCode方法。来保证相同的Map集合 它们的hashCode值必须也是相同的
    int hashCode();

    // 遍历集合中所有键值对元素,使它们都调用action的accept方法
    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);
        }
    }

    // 表示键值对实体的顶层接口。
    // 它提供了获取键值getKey方法,获取value值的getValue方法,已经替换value值的setValue方法。
    interface Entry<K,V> {
        // 获取key值
        K getKey();

        // 获取value值
        V getValue();

        // 替换value值
        V setValue(V value);

        // 比较两个Entry是否相等的方法
        boolean equals(Object o);

        // 因为复写了equals方法,必须复写hashCode方法。来保证相同的Entry 它们的hashCode值必须也是相同的
        int hashCode();

        // 工具类方法, 返回一个根据key值大小的比较器Comparator。
        // 调用Comparator的compare(T o1, T o2)方法,就可以比较两个Map.Entry大小,是根据Map.Entry的key值来判断的。
        // 所以必须保证key值的类型是实现Comparable接口,来确保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());
        }

        // 工具类方法,返回一个根据value值大小的比较器Comparator。
        // 所以必须保证value值的类型是实现Comparable接口,来确保value值是可以比较的
        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());
        }

        // 工具类方法,根据一个key值比较器Comparator<? super K> 生成一个 键值对比较器Comparator<Map.Entry<K, V>>
        // 很简单,因为键值对比较器Comparator的compare(T o1, T o2)方法 参数类型是Map.Entry,
        // 然后调用Map.Entry的getKey方法获取对应key值,然后调用key值比较器Comparator的compare方法就实现比较功能了。
        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());
        }

        // 工具类方法,根据一个value值比较器Comparator<? super V> 生成一个 键值对比较器Comparator<Map.Entry<K, V>>
        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());
        }
    }

}

总结

Map接口不继承Collection接口,它与Collection没有任何联系。甚至它都没有继承Iterable接口,说明Map是不可迭代的。

操作元素

1. 添加元素

  • V put(K key, V value); 向集合中添加 键值对,但是如果key值存在,就替换对应的value值。
  • void putAll(Map<? extends K, ? extends V> m); 将集合m中的键值对全部存放到本集合中。
  1. 删除元素
  • V remove(Object key); 根据键key移除Map集合中对应的键值对, 并返回value值
  • void clear(); 清除集合中所有的键值对

注意 和Collection接口不一样,它没有提供removeAll以及retainAll的方法。

  1. 查询方法 中最主要的方法是Set<Map.Entry<K, V>> entrySet();返回键值对的Set集合,我们通过它才能遍历Map集合,因为Map集合本身是不支持遍历的。至于keySet集合以及values集合都是通过这个entrySet集合得到的。

Map内部接口Entry

提供键值对顶层接口Entry,具体实现交给子类。
主要是提供了访问键值对的三个重要方法:

  1. K getKey(); 获取key值。
  2. V getValue(); 获取value值
  3. V setValue(V value); 替换value值

Map接口提供默认实现方法

主要有两个方法需要理解:

  1. V get(Object key); 这个方法会遍历Map集合,寻找key相同的键值对元素,然后返回对应的value值,如果没有找到,就返回null。

所以我们可以断定如果返回值不为null,那么这个key值在Map集合中一定存在。但是坑爹的是如果返回为null,并不代表这个key值在Map集合中不存在,因为键值对的value 值是可以为null的。

  1. boolean containsKey(Object key); 如果key值在Map集合中存在就返回true,如果不存在就返回false。

这个方法可以判断key值在集合中是否存在,但是它有个缺陷,它不能返回key值对应的value值,有时候我们需要这个值,这个时候我们就必须调用V get(Object key)方法。这个两个方法都是遍历整个Map集合进行寻找,所以效率不高。

因此我们一般先调用 V get(Object key)方法,如果得到值不为null,那么这个key值在Map集合中存在,如果为null,再调用containsKey(Object key)方法。一般判断式(((v = get(key)) != null) || containsKey(key))

如果我们不单单比较key值,需要键值对都相等,操作流程如下:

  • 先通过get(Object key)查找curValue值
  • 然后判断这个curValue值与传入的value是否相等,不相等就直接返回。
  • 相等的话,就判断key值在集合中是否存在。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容

  • title: Java集合框架Mapdate: 2017-08-09 14:38:12tags: 集合框架cate...
    徐笔笔阅读 362评论 1 6
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,257评论 0 16
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,628评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,488评论 0 3