Java 集合学习笔记:Map

UML

在这里插入图片描述

简介

将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
.
此接口取代 Dictionary 类,后者完全是一个抽象类,而不是一个接口。
.
Map 接口提供三种 collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。
.
注:将可变对象用作映射键时必须格外小心。当对象是映射中某个键时,如果以影响 equals 比较的方式更改了对象的值,则映射的行为将是不确定的。此项禁止的一种特殊情况是不允许某个映射将自身作为一个键包含。虽然允许某个映射将自身作为值包含,但请格外小心:在这样的映射上 equalshashCode 方法的定义将不再是明确的。
.
所有通用的映射实现类应该提供两个“标准的”构造方法:一个 void(无参数)构造方法,用于创建空映射;一个是带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是 JDK 中所有通用的映射实现都遵从它。
.
此接口中包含的“破坏”方法可修改其操作的映射,如果此映射不支持该操作,这些方法将抛出 UnsupportedOperationException。如果是这样,那么在调用对映射无效时,这些方法可以(但不要求)抛出 UnsupportedOperationException。例如,如果某个不可修改的映射(其映射关系是“重叠”的)为空,则对该映射调用 putAll(Map) 方法时,可以(但不要求)抛出异常。
.
某些映射实现对可能包含的键和值有所限制。例如,某些实现禁止 null 键和值,另一些则对其键的类型有限制。尝试插入不合格的键或值将抛出一个未经检查的异常,通常是 NullPointerExceptionClassCastException。试图查询是否存在不合格的键或值可能抛出异常,或者返回 false;某些实现将表现出前一种行为,而另一些则表现后一种。一般来说,试图对不合格的键或值执行操作且该操作的完成不会导致不合格的元素被插入映射中时,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此接口的规范中标记为“可选”。
.
此接口是 Java Collections Framework 的成员。
.
Collections Framework 接口中的很多方法是根据 equals 方法定义的。例如,containsKey(Object key) 方法的规范中写道:“当且仅当此映射包含针对满足 (key==null ? k==null : key.equals(k)) 的键 k 的映射关系时,返回 true”。不 应将此规范解释为:调用具有非空参数 keyMap.containsKey 将导致对任意的键 k 调用 key.equals(k)。实现可随意进行优化,以避免调用 equals,例如,可首先比较两个键的哈希码(Object.hashCode() 规范保证哈希码不相等的两个对象不会相等)。一般来说,只要实现者认为合适,各种 Collections Framework 接口的实现可随意利用底层 Object 方法的指定行为。

源码阅读

嵌套类

interface Entry<K,V>

https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html
Map 中存放的是 EntryEntry 中再存放 Key、Value
在这里插入图片描述

HashMap 中有实现 static class Node<K,V> implements Map.Entry<K,V> 详情看 HashMap 学习笔记。

静态方法

访问修饰符&返回类型方法描述
static <K extends Comparable<? super K>,V> Comparator<Map.Entry<K,V>>comparingByKey()返回一个比较器。按 key 升序排列 Map.Entry
static <K,V> Comparator<Map.Entry<K,V>>comparingByKey(Comparator<? super K> cmp)返回一个比较器,使用给定的 key 比较 Map.Entry
static <K,V extends Comparable<? super V>> Comparator<Map.Entry<K,V>>comparingByValue()返回一个比较器。按 value 升序排列 Map.Entry
static <K,V> Comparator<Map.Entry<K,V>>comparingByValue(Comparator<? super V> cmp)返回一个比较器,使用给定的 value 比较 Map.Entry
booleanequals(Object o)比较指定对象与此项的相等性。
KgetKey()返回与此项对应的键。
VgetValue()返回与此项对应的值。
inthashCode()返回此映射项的哈希码值。
VsetValue(V value)用指定的值替换与此项对应的值(可选操作)。

comparingByKey()

默认使用 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());
}

comparingByKey(Comparator<? super K> cmp)

使用 key 按给定的比较规则 cmp 比较。返回的比较器,被强转为可序列化的。

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

comparingByValue()

默认使用 vale自然升序 比较。返回的比较器,被强转为可序列化的。

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

comparingByValue(Comparator<? super V> cmp)

使用 value 按给定的比较规则 cmp 比较。返回的比较器,被强转为可序列化的。

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

比较器

Interface Comparable< T >

https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html

访问修饰符&返回类型方法描述
intcompareTo(T o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

默认方法

访问修饰符&返回类型方法描述
voidclear()从此映射中移除所有映射关系(可选操作)。
default Vcompute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)对给定 K使用映射函数mappingFunction进行更新并返回新值。如果 新值null,执行 remove(key) 并返回 null
defaultVcomputeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)如果值不存在或为null,使用 mappingFunction 的结果赋值,如果赋值成功,返回新值,否则返回 null 。(mappingFunction结果为 null 时直接返 null
defaultVcomputeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)如果值存在非null,使用 mappingFunction 计算新值。如果新值非null更新并返回新值,否则 remove(key) 并返回null
booleancontainsKey(Object key)如果此映射包含指定键的映射关系,则返回 true。
booleancontainsValue(Object value)如果此映射将一个或多个键映射到指定值,则返回 true。
Set<Map.Entry<K,V>>entrySet()返回此映射中包含的映射关系的 Set 视图。
booleanequals(Object o)比较指定的对象与此映射是否相等。
default voidforEach(BiConsumer<? super K,? super V> action)两个入参的消费者,默认按 entrySet 顺序逐个消费。
Vget(Object key)返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
default VgetOrDefault(Object key, V defaultValue)如果 key 找不到映射的 valuenull也算有)。则返回给定的默认值
inthashCode()返回此映射的哈希码值。
booleanisEmpty()如果此映射未包含键-值映射关系,则返回 true。
SetkeySet()返回此映射中包含的键的 Set 视图。
default Vmerge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)不存在或为null时用 value 赋值,否则用 mappingFunction 更新。如 mappingFunction 返回null等同于remove
Vput(K key, V value)将指定的值与此映射中的指定键关联(可选操作)。
voidputAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)。
default VputIfAbsent(K key, V value)如果值存在非空执行 get(k),否则执行put(k, v)并返回其结果。
Vremove(Object key)如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
default booleanremove(Object key, Object value)键、值都匹配则删除。
default Vreplace(K key, V value)有对应值就执行替换。
default booleanreplace(K key, V oldValue, V newValue)如果key确实映射到 oldValue 则替换值为newValue
default voidreplaceAll(BiFunction<? super K,? super V,? extends V> function)调用给定函数BiFun,遍历替entryBiFun返回值替换元素的值,直到完成所有或抛异常。
intsize()返回此映射中的键-值映射关系数。
Collectionvalues()返回此映射中包含的值的 Collection 视图。

Java8 开始引入了默认方法概念,允许向接口中添加默认实现。(用来兼容升级接口)
以下都默认方法。

compute(K key, BiFunction<K, V, V> fun)

  • test
    @Test
    public void computeTest(){
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);

        Object result = map.compute("a", (k, v) -> k + " : " + v);
        System.out.println("原值:" + result);          // 1
        System.out.println("更新后:" +map);            // {a=a : 1}

        result = map.compute("b", (k, v) -> null);
        System.out.println("原值:" + result);          //  null
        System.out.println("更新后:" +map);            // {a=a : 1}


        result = map.compute("a", (k, v) -> null);
        System.out.println("原值:" + result);          // null
        System.out.println("更新后:" +map);            // {}
    }
  • compute
    default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    	// 检测给定的 lambda 方法 remappingFunction 如果为 null 就抛锅。
        Objects.requireNonNull(remappingFunction);
        // 先取出旧值缓存。
        V oldValue = get(key);
		// 对旧值应用给定的 lambda 方法算出新值
        V newValue = remappingFunction.apply(key, oldValue);
        // 如果新值为空,则移除原键值对,返回 null
        // 否则新值不为空,则:用新值替换旧值,并返回新值。
        if (newValue == null) {
        	// 新值为空则,返回 null
        	// 但如果原 key 或 value 存在,则需要先删除原键值对,再返回 null
            if (oldValue != null || containsKey(key)) { 
                remove(key);
                return null;
            } else { 
                return null;
            }
        } else {
            put(key, newValue);
            return newValue;
        }
    }

按理说,新值是否为 null 触发删除逻辑这段不是应该过样写么

		// 不为空,替换,并返回新值
        if (newValue != null) {
            put(key, newValue);
            return newValue;
        }
        // 否则新值为空,key存在或旧值不为空,执行删除。
        if (oldValue != null || containsKey(key)) { 
            remove(key);
        } 
        // 最后返回 null
        return null;

computeIfAbsent(K key, Function<K, V> fun)

    @Test
    public void computeIfAbsentTest() {
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);

        Object a = map.computeIfAbsent("a", key -> DateUtil.today());
        Object b = map.computeIfAbsent("b", key -> DateUtil.today());
        Object c = map.computeIfAbsent("c", key -> null);

        System.out.println(a);   // 1
        System.out.println(b);   // 2022-11-16
        System.out.println(c);   // null
        System.out.println(map); // {a=1, b=2022-11-16}
    }
  • computeIfAbsent
    default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        // 检测给定的 lambda 方法 remappingFunction 如果为 null 就抛锅。
        Objects.requireNonNull(mappingFunction);
        // 声明返回变量
        V v;
        // 获取 key 对应的值,如果值存在,直接返回
        // 否则 mappingFunction 生成一个新值填充。并返回新值
        // 如果生成的也是 null 直接返回 null
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }

computeIfPresent(K key, BiFunction<K, V, V> fun)

  • test
    @Test
    public void computeIfPresentTest(){
        Map<String, Integer> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);

        // 值不为空,替换新值
        Integer result = map.computeIfPresent("a", (k, v) -> 2 );
        System.out.println("原值:" + result);          //  2
        System.out.println("更新后:" +map);            // {a=2, b=null}

        // 值为 null,啥也不做,直接返回 null
        result = map.computeIfPresent("b", (k, v) -> 666 );
        System.out.println("原值:" + result);          //  null
        System.out.println("更新后:" +map);            // {a=2, b=null}

        // 按 key 找到值,执行 lambda 得到新值为空,移除键值对
        result = map.computeIfPresent("a", (k, v) -> null );
        System.out.println("原值:" + result);          //  null
        System.out.println("更新后:" +map);            // {b=null}
    }
  • computeIfPresent
    default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    	// 非空检测,如果为 null 就抛锅。
        Objects.requireNonNull(remappingFunction);
        // 声明临时变量用于存放旧值
        V oldValue;
        // 按 key 获取对应值,并付给 oldValue,如果为空直接返回 null
        // 否则使用给定的 remappingFunction 处理原键值对,得出新值 newValue 
        // 新值不为空:调用 put 修改为 newValue。并返回 newValue
        // 否则移除 键值对。返回 null
        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;
        }
    }

forEach(BiConsumer<K, V> action)

  • test
    @Test
    public void forEachTest(){
        Map<String, Integer> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.forEach((k,v) -> {
            System.out.println( "键="+ k + "; 值=" + v);
        });
    }
  • forEach
	default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        // 遍历 map 中的 键值对。
        // 通过 entrySet() 拿到一个 Set<Entry>,set 的每个 Entry 元素就是一个键值对
        // 从 Entry 中分别取出键和值,使用 lambda 函数 action 消费 k, v
        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);
        }
    }

getOrDefault(Object key, V defaultValue)

  • test
    @Test
    public void getOrDefaultTest(){
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);
        System.out.println(map.getOrDefault("a", "默认值1")); // 1
        System.out.println(map.getOrDefault("b", "默认值2")); // null
        System.out.println(map.getOrDefault("c", "默认值3")); // 默认值3
    }
  • getOrDefault
	default V getOrDefault(Object key, V defaultValue) {
        V v;
        // 取到值就返回(哪怕是 null 也行)
        // 如果没取到,就返回给定的默认值 defaultValue
        return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
        // 值为 null 有两种情况:
        // key 对应的值确实是 null
        // key 不存在。
    }

拆解 (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;

V v = get(key);
if(v != null) || containsKey(key)){
	return v;
}else{
	return defaultValue;
}

merge(K key, V value, BiFunction<V, V, V> fun)

  • test
    @Test
    public void mergeTest(){
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);
        System.out.println(map.merge("a", 10, (k,v) -> "更新")); // 更新
        System.out.println(map.merge("b", 22, (k,v) -> "xxxx")); // 22
        System.out.println(map.merge("x", 99, (k,v) -> "xxxx")); // 99
        System.out.println(map);    // {x=99, a=更新, b=22}
    }
  • merge
	default V merge(K key,
					V value,
            		BiFunction<? super V, ? super V, ? extends V> remappingFunction
    ) {
    	// 非空检测,如果为 null 就抛锅。
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        // 取出原值临时保存在 oldValue
        // 计算新值 newValue
        // oldValue 为空使用传进来的给定什 value
        // 否则使用 remappingFunction 计算新值。
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
        // 新值为空执行 remove(key); 否则执行 put(key, newValue);
        if(newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        // 最后返回新值
        return newValue;
    }

putIfAbsent(K key, V value)

  • test
    @Test
    public void putIfAbsentTest(){
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);
        System.out.println(map.putIfAbsent("a", 10)); // 1
        System.out.println(map.putIfAbsent("b", 20)); // null
        System.out.println(map.putIfAbsent("c", 20)); // null
        System.out.println(map);                      // {a=1, c=20, b=20}
    }
  • putIfAbsent
	default V putIfAbsent(K key, V value) {
		// 取值,如果非 null 直接返回。
		// 否则执行 put 并返回其结果。
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
        return v;
    }

remove(Object key, Object value)

  • test
    @Test
    public void removeTest(){
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);
        System.out.println(map.remove("a", 10));    // false
        System.out.println(map);                    // {a=1, b=null}
        System.out.println(map.remove("a", 1));     // true
        System.out.println(map);                    // {b=null}
        System.out.println(map.remove("b", 20));    // false
        System.out.println(map);                    // {b=null}
        System.out.println(map.remove("b", null));  // true
        System.out.println(map);                    // {}
    }
  • remove
	default boolean remove(Object key, Object value) {
		// 获取当前值
        Object curValue = get(key);
        // 如果当前值与给定值不相等,直接返回 false
        // 或者如果当前值为 null 并且当前 map 中不存在 key ,直接返回 false
        if (!Objects.equals(curValue, value)
         	|| (curValue == null && !containsKey(key))) 
        {           
        	return false;
        }
        // 移除
        remove(key);
        return true;
    }

尝试改写,更符合语义。官方的写法尽量避免 containsKey 是为了提升效率。

	default boolean remove(Object key, Object value) {
        if (containsKey(key) && Objects.equals(get(key), value)) {     
        	remove(key);      
        	return true;
        }       
        return false;
    }

replace(K key, V value)

  • test
    @Test
    public void replaceTest() {
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);
        
        Object a = map.replace("a", 11);
        System.out.println(a);          // 1
        System.out.println(map);        // {a=11, b=null}
        
        Object b = map.replace("b", 2);
        System.out.println(b);          // null
        System.out.println(map);        // {a=11, b=2}
        
        Object c = map.replace("c", 3);
        System.out.println(c);          // null
        System.out.println(map);        // {a=11, b=2}
    }
  • replace(K key, V value)
	default V replace(K key, V value) {
        V curValue; //  null
        // 取出当前值,如果非空或者 key 存在,执行 put 并返回结果
         if (((curValue = get(key)) != null) || containsKey(key)) {
            curValue = put(key, value);
        }
        // 否则直接返回
        return curValue;
    }

replace(K key, V oldValue, V newValue)

  • test
@Test
    public void replace3Test() {
        Map<Object, Object> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", null);

        Object a = map.replace("a", 2, 11);
        System.out.println(a);          // false
        System.out.println(map);        // {a=1, b=null}

        a = map.replace("a", 1, 111);
        System.out.println(a);          // true
        System.out.println(map);        // {a=111, b=null}
        
        Object b = map.replace("b", 2, 22);
        System.out.println(b);          // false
        System.out.println(map);        // {a=111, b=null}       
        
        b = map.replace("b", null, 222);
        System.out.println(b);          // true
        System.out.println(map);        // {a=111, b=222}

        Object c = map.replace("c", null, 333);
        System.out.println(c);          // false
        System.out.println(map);        // {a=111, b=222}
    }
  • boolean replace(K key, V oldValue, V newValue)
	default boolean replace(K key, V oldValue, V newValue) {
		// 取出当前值
        Object curValue = get(key);
        // 如果当前值与给定的 oldValue 不同,直接返回 false
        // 或者当前值为空且key不存在,直接返回 false
        if (!Objects.equals(curValue, oldValue) || (curValue == null && !containsKey(key))) {
            return false;
        }
        // 否则更新为 newValue
        put(key, newValue);
        return true;
    }

尝试改写,更符合语义。官方的写法尽量避免 containsKey 是为了提升效率。

	default boolean replace(K key, V oldValue, V newValue) {
        if (containsKey(key) && Objects.equals(get(key), oldValue)) {
            put(key, newValue);
        	return true;
        }
        return false;
    }

replaceAll(BiFunction<K, V, V> function)

  • test
    @Test
    public void replaceAllTest() {
        Map<String, Integer> map = new WeakHashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.replaceAll((k,v) -> v * 10 );
        System.out.println(map);    // {a=10, b=20}
    }
  • replaceAll
	default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
		// 非空检测,如果为 null 就抛锅。
        Objects.requireNonNull(function);
        // 遍历所有键值对,并更新值
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // 取不到 k 或 v 说明可能被别人动了。抛锅。
                throw new ConcurrentModificationException(ise);
            }
			// 执行映射函数并获得结果
            v = function.apply(k, v);

            try {
                entry.setValue(v);
            } catch(IllegalStateException ise) {
                // entry 不存了在,抛锅
                throw new ConcurrentModificationException(ise);
            }
        }
    }

参考资料

Interface Map<K,V>
Interface Map.Entry<K,V>
Class WeakHashMap<K,V>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑虾

多情黯叹痴情癫。情癫苦笑多情难

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值