专栏首页Vi的技术博客Java基础系列(四十五):集合之Map

Java基础系列(四十五):集合之Map

简介

Map是一个接口,代表的是将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值。

Map 接口提供了三种collection视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。

下面,我们去通过源码中看一看Map都给我们提供了哪些功能,以及一些方法的用法。

增 or 改:

    /**
     * 将指定的值与此映射中的指定键关联。
     * 如果此映射以前包含一个该键的映射关系,则用指定值替换旧值
     * @param key   与指定值关联的键
     * @param value 与指定键关联的值
     * @return  以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
     */
    V put(K key, V value);

    /**
     * 从指定映射中将所有映射关系复制到此映射中
     * @param m  要存储在此映射中的映射关系
     */
    void putAll(Map<? extends K, ? extends V> m);

可以看出,在 Java 8 之前提供了这两个向映射中添加映射关系的方法,这两个方法我们已经耳熟能详,下面我们来看一下查看元素的方法。

查:

/**
 * 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
 * @param key   要返回其关联值的键
 * @return  指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
 */
V get(Object key);

这里的前提是你必须知道映射中的键,才能获取映射中的值。但是我们在前面说过,Map接口提供了三个collection的视图,我们可以使用这些视图来去获取Map中的元素

/**
 * 返回此映射中包含的键的 Set 视图。
 * @return  此映射中包含的键的 set 视图
 */
Set<K> keySet();

/**
 * 返回此映射中包含的值的 Collection 视图。
 * @return  此映射中包含的值的 collection 视图
 */
Collection<V> values();

/**
 * 返回此映射中包含的映射关系的 Set 视图。
 * @return  此映射中包含的映射关系的 set 视图
 */
Set<Entry<K, V>> entrySet();

当然,还有在 Java 8 新增的forEach方法也可以遍历获取Map中的值

/**
 * 遍历集合,这里的参数是一个函数式接口,可以结合Lambda表达式去优雅的使用
 * @param action    进行的操作,函数式接口
 */
default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    //其实本质上还是用entrySet()获取键值对后进行遍历的
    for (Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch(IllegalStateException ise) {
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

我们可以这样去使用forEach方法:

Map<String, String> map = new HashMap<>();
map.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v));

当然,我们可以这样的去优雅去遍历一个集合:

//获取key视图
map.keySet().forEach(s -> System.out.println("Key:Value=" + s + ":" + map.get(s)));
//获取键值对视图
map.entrySet().forEach(entry -> System.out.println("Key:Value=" + entry.getKey() + ":" + entry.getValue);
//获取值视图
map.values.forEach(s -> System.out.println("Value" + s));

下面我们来看一下Map的删:

删:

     /**
     * 如果存在一个键的映射关系,则将其从此映射中移除
     * @param key   从映射中移除其映射关系的键
     * @return  以前与 key 关联的值;如果没有 key 的映射关系,则返回 null。
     */
    V remove(Object key);

     /**
     * 从此映射中移除所有映射关系,该方法被调用后,该映射将为空。
     */
    void clear();

hashCode()equals()也在Map中被重新定义了:

     /**
     * 比较指定的对象与此映射是否相等。如果给定的对象也是一个映射,并且这两个映射表示相同的映射关系,则返回 true。更确切地讲,
     * 如果 m1.entrySet().equals(m2.entrySet()),则两个映射 m1 和 m2 表示相同的映射关系。这可以确保 equals 方法在不同的 Map 接口实现间运行正常。
     * @param o
     * @return
     */
    @Override
    boolean equals(Object o);


    /**
     * 返回此映射的哈希码值。映射的哈希码定义为此映射 entrySet() 视图中每个项的哈希码之和。
     * 这确保 m1.equals(m2) 对于任意两个映射 m1 和 m2 而言,都意味着 m1.hashCode()==m2.hashCode(),正如 Object.hashCode() 常规协定的要求。
     * @return
     */
    @Override
    int hashCode();

在这里只是定义了一个接口,而在具体的实现时,必须遵循这里所规定的一些规则,映射的哈希码定义为此映射 entrySet() 视图中每个项的哈希码之和。这样才能保证equals 方法在不同的 Map 接口实现间运行正常。

在 Java 8 之后,新增了一些default方法可以配合lambda表达式去使用,我们一起来看一下这几个方法:

JDK1.8新特性

         /**
         * 根据映射的键进行排序
         */
        public static <K extends Comparable<? super K>, V> Comparator<Entry<K,V>> comparingByKey() {
            return (Comparator<Entry<K, V>> & Serializable)
                    (c1, c2) -> c1.getKey().compareTo(c2.getKey());
        }

         /**
         * 通过指定的比较器根据映射的键进行排序
         */
        public static <K, V> Comparator<Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Entry<K, V>> & Serializable)
                    (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
        }

首先来说的是Map的子接口Entry中的comparingByKey()方法,这个方法所起到的作用是按照映射的键进行排序,我们接下来来看一下怎么取用:

public class Test {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String,String>();
        map.put("A","test1");
        map.put("B","test2");
        map.put("E","test5");
        map.put("D","test4");
        map.put("C","test3");
        Stream<Map.Entry<String, String>> sorted = map.entrySet().stream().sorted(Map.Entry.comparingByKey());
        Stream<Map.Entry<String, String>> sorted2 = map.entrySet().stream().sorted(Map.Entry.comparingByKey(String::compareTo));
        sorted.forEach(entry -> System.out.println(entry.getValue()));
        System.out.println("===============");
        sorted2.forEach(entry -> System.out.println(entry.getValue()));
    }
}

输出结果为:

test1
test2
test3
test4
test5
===============
test1
test2
test3
test4
test5

可以看到,这是被排序后的结果,至于comparingByValue方法,其实用法和comparingByKey()基本上一样,所以这里不再多做解说,forEach的用法,我们在前面已经进行了了解,下面,我们来看一下getOrDefault()方法

/**
 * 如果映射中存在于key相对应的value,则返回这个value,否则返回defaultValue
 * @param key   指定值的键,如果该value不存在,返回defaultValue
 * @param defaultValue  如果指定键的值不存在,返回这个值
 * @return  如果映射中存在于key相对应的value,则返回这个value,否则返回defaultValue
 */
default V getOrDefault(Object key, V defaultValue) {
    V v;
    return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
}

下面,我写了一个小demo来让大家看看这个方法怎么用

public static void main(String[] args) {
        Map<String, String> map = new HashMap<String,String>();
        map.put("A","test1");
        map.put("B","test2");
        String value = map.getOrDefault("A", "test2");
        System.out.println(value);
        String defaultValue = map.getOrDefault("C", "test1");
        System.out.println(defaultValue);
}

输出的结果为:

test1
test1    

可以看出,第一个获取到的是键“A”对应的值“test1”,第二次调用获取的是defaultValue---“test1”。下面,我们来看一下replace家族的一些成员:

    /**
     * 对映射中的所有键值对执行计算,并将返回结果作为value覆盖
     * map.replaceAll((k,v)->((String)k).length());
     * @param function  执行的操作,函数式接口
     */
    default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        ...
    }

    /**
     * 当且仅当 key 存在,并且对应值与 oldValue 不相等,才用 newValue 作为 key 的新相关联值,返回值为是否进行了替换。
     * @param key   与指定值相关联的键
     * @param oldValue  预期与指定键相关联的值
     * @param newValue  与指定键相关联的值
     * @return  如果该值被替换,返回true
     */
    default boolean replace(K key, V oldValue, V newValue) {
        ...
    }


    /**
     * 只有当目标映射到某个值时,才能替换指定键的条目。
     * @param key    与指定值相关联的键
     * @param value 与指定键相关联的值
     * @return  与指定键相关联的上一个值,如果没有键的映射,返回null
     */
    default V replace(K key, V value) {
       ...
    }

接下来,我写了一个小demo来看看这三个方法的用法:

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String,String>();
        map.put("A","test1");
        map.put("B","test2");
        map.replaceAll((s, s2) -> {
            return s + s2;
        });
        printMap(map);
        map.replace("A","test1");
        printMap(map);
        map.replace("A","test2","test1");
        printMap(map);
        map.replace("A","test1","test2");
        printMap(map);
    }

    public static void printMap(Map<String,String> map){
       map.forEach((key, value) -> System.out.print(key + ":" + value + "    "));
        System.out.println();
    }

打印结果:

A:Atest1    B:Btest2    
A:test1    B:Btest2    
A:test1    B:Btest2    
A:test2    B:Btest2    

一切正如我们所想象的那般发展,接下来,我们来看一下compute三兄弟。

    /**
     * 如果指定的键尚未与值相关联(或映射到null),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非null 。
     * @param key   指定值与之关联的键
     * @param mappingFunction   计算值的函数
     * @return  与指定键相关联的当前(现有或计算)值,如果计算值为空,则为null
     */
    default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
          ...
    }

    /**
     * 如果指定的key的值存在且非空,则尝试计算给定键及其当前映射值的新映射。
     * @param key   指定值与之关联的键
     * @param remappingFunction 计算值的函数
     * @return  与指定键相关的新值,如果没有则为null
     */
    default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
          ...
    }


    /**
     * 尝试计算指定key及其当前映射值的映射(如果没有当前映射,则null )。
     * @param key   指定值与之关联的键
     * @param remappingFunction 计算值的函数
     * @return 与指定键相关的新值,如果没有则为null
     */
    default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
       ...
    }

现在,我们来看一下,这三个方法是怎么用的,以及他们的不同。

 public static void main(String[] args) {
        Map<String, String> map = new HashMap<String,String>();
        map.put("A","test1");
        map.put("B","test2");
        map.compute("A", (key, value) -> { return key + value;});
        printMap(map);
        //因为,集合中存在“A”,所以这里没有进行相应的操作
        map.computeIfAbsent("A", (key) -> { return key + 2;});
        printMap(map);
         //这里因为集合中不存在“C”,所以进行了赋值的操作
        map.computeIfAbsent("C", (key) -> { return key + 2;});
        printMap(map);
         //这里由于集合存在“A”,根据方法定义,会计算后返回给原值
        map.computeIfPresent("A", (key, value) -> { return key + value;});
        printMap(map);
         //这里由于不存在“D”,根据方法定义,不做任何操作
          map.computeIfPresent("D", (key, value) -> { return key + value;});
        printMap(map);
    }

    public static void printMap(Map<String,String> map){
       map.forEach((key, value) -> System.out.print(key + ":" + value + "    "));
        System.out.println();
    }

输出结果:

A:Atest1    B:test2    
A:Atest1    B:test2    
A:Atest1    B:test2    C:C2    
A:AAtest1    B:test2    C:C2    
A:AAtest1    B:test2    C:C2  

那么,最后剩下的还有一个类似于computemerge方法以及remove方法和putIfAbsent方法,接下来我们来看看这几个方法。

    /**
     * 如果key在集合中的value为空或则键值对不存在,则用参数value覆盖
     * @param key   如果key存在且不为null,返回key对应的value,如果不存在,调用put(key,value)
     * @param value 如果key对应的值不存在或者为null,将该value与key进行对应
     * @return  返回的是被替代的值
     */
    default V putIfAbsent(K key, V value) {
       ...
    }

     /**
     * key 与 value 都匹配时才删除。
     * @param key   被删除的映射关系的key
     * @param value 被删除的映射关系的value
     * @return  返回的是否删除成功
     */
    default boolean remove(Object key, Object value) {
       ...
    }

     /**
     * 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。
     * @param key   结合值与之关联的键
     * @param value 要与与key相关联的现有值合并的非空值,或者如果没有现有值或空值与key相关联,则与该key相关联
     * @param remappingFunction 重新计算值(如果存在)的功能
     * @return 与指定键相关联的新值,如果没有值与该键相关联,则返回null
     */
    default V merge(K key, V value,
                    BiFunction<? super V, ? super V, ? extends V> remappingFunction) {

    }

接下来,我们接着来看一个例子:

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String,String>();
        map.put("A","test1");
        map.put("B","test2");
        map.putIfAbsent("A","test2");
        map.putIfAbsent("C","test3");
        printMap(map);
        map.remove("A","test1");
        printMap(map);
        map.merge("A","test1",(oldValue, newValue) ->{
            return oldValue + newValue;
        } );
        printMap(map);
        map.merge("A","test4",(oldValue, newValue) ->{
            return  newValue;
        } );
        printMap(map);
    }

输出的是:

A:test1    B:test2    C:test3    
B:test2    C:test3    
A:test1    B:test2    C:test3    
A:test4    B:test2    C:test3 

到这里,Map的讲解就要和大家告一段落了,下篇AbstractMap再见~

原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

本文分享自微信公众号 - Vi的技术博客(viyoungblog),作者:ViYoung

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM(二):画骨

    我们首先来认识一下JVM的运行时数据区域,如果说JVM是一个人,那么运行时数据区域就是这个人的骨架,它支撑着JVM的运行,所以我们先来学习一下运行时数据区域的分...

    山禾说
  • Java基础系列(四十八):集合之HashMap

    说起HashMap,大家肯定都不会陌生,我们用的最多的大概就是这个容器类来存储k-v数据,正如它的名字所说的那样,它是基于散列表实现的,散列表的强大之处在于查找...

    山禾说
  • Spring Boot 2.x(八):日志框架的使用

    最初我们开始接触Java的时候,我们通常会使用System.out.println()将我们想要知道的信息打印到控制台。 但是,如果在服务器上我们去运行我们的J...

    山禾说
  • ES6 学习笔记之 map 的多种遍历方法

    Map 是 ES6 中新增的一种数据结构,与 Set 一起添加,其实功能都差不多。我就只列举了 Map 的遍历方式,一通百通,其自身内部带有的的方法看书中介绍即...

    我与梦想有个约会
  • 有关于Java Map,应该掌握的8个问题

    最近几天看了几篇有关于Java Map的外国博文,写得非常不错,所以整理了Java map 应该掌握的8个问题,都是日常开发司空见惯的问题,希望对大家有帮助;如...

    捡田螺的小男孩
  • 有关于Java Map,应该掌握的8个问题

    最近几天看了几篇有关于Java Map的外国博文,写得非常不错,所以整理了Java map 应该掌握的8个问题,都是日常开发司空见惯的问题,希望对大家有帮助;如...

    好好学java
  • SAP云平台上的Fiori Launchpad tile数据是如何从后台取出来的

    After I log on HCP via my own user, I could see many tiles: https://fiorilaunch...

    Jerry Wang
  • 部署在云上的Fiori launchpad,其tile信息是从哪里取出来的

    版权声明:本文为博主汪子熙原创文章,未经博主允许不得转载。 https://jerry.bl...

    Jerry Wang
  • 哈希Map合并工具类

    有两个哈希Map,如果要实现Map追加的话,可以使用putAll()方法,不可以使用put()方法,但是如果出现两个Map有相同的key,但是值不同,这种情况就...

    SmileNicky
  • JDK源码分析-Map

    Map 是一个接口,它表示一种“键-值(key-value)”映射的对象(Entry),其中键是不重复的(值可以重复),且最多映射到一个值(可以理解为“映射”或...

    WriteOnRead

扫码关注云+社区

领取腾讯云代金券