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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vi_young_95/article/details/84055852

简介

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再见~

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


公众号

猜你喜欢

转载自blog.csdn.net/vi_young_95/article/details/84055852