Java 集合学习笔记:Map

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:Map相关的知识,希望对你有一定的参考价值。

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 开始引入了默认方法概念,允许向接口中添加默认实现。(用来兼容升级接口)
以下都默认方法。

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

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

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;
        
    

void 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 + "; 值="以上是关于Java 集合学习笔记:Map的主要内容,如果未能解决你的问题,请参考以下文章

Java学习笔记之:Java Map集合

Java学习笔记32(集合框架六:Map接口)

Go语言学习笔记十三: Map集合

java学习笔记—集合之Map集合

java集合学习笔记

Java 集合学习笔记:比较器 ComparableComparator