只会put、get?快来学Map这几个“新”方法

引子

Map 的数据操作,你是不是还只会putget
Map 是我们日常编程中十分常用的数据接口,的在 JDK8 中,Map 引入了几个新方法,可以简化我们对 Map 中数据的操作。目前 JDK 的最新 LTS 版本已经更新到 21 了,这几个在 JDK8 引入的 Map”新“方法其实也是”老“方法了,还没熟练使用也太 out 了,快来看看你都”学废“了吗?

只会put、get?快来学Map这几个“新”方法

getOrDefault 

这个方法名很直观,见名知意:尝试获取 key 对应的值,如果未获取到,就返回默认值。
看一个使用的例子,新写法会比老写法更加简洁:

private static void testGetOrDefault() {
    Map<String, String> map = new HashMap<>(4);
    map.put("123""123");
    String key = "key";
    String defaultValue = "defaultValue";

    // 老写法
    String oldValue = defaultValue;
    if (map.containsKey(key)) {
        oldValue = map.get(key);
    }
    System.out.println("oldValue = " + oldValue);

    // 新写法
    String newValue = map.getOrDefault(key, defaultValue);
    System.out.println("newValue = " + newValue);
}

foreach 

看方法名也可以知道,这个方法是遍历 map 的数据使用的。
如果没有 foreach,我们遍历 map 的时候一般是使用增强 for 循环,有了这个方法后,可以更加方便使用 entry 中的 key 和 val:

private static void testForeach() {
    Map<String, String> map = new HashMap<>(4);
    map.put("123""123");

    // 老写法
    for (Map.Entry<String, String> entry : map.entrySet()) {
        System.out.printf("老写法 key = %s, value = %s%n", entry.getKey(), entry.getValue());
    }

    // 新写法
    map.forEach((key, value) -> System.out.printf("新写法 key = %s, value = %s%n", key, value));
}

merge 

从名字可以想到,是合并 entry 使用的,但是具体是怎么合并呢?
看一下日常最常用的 Map 实现类 HashMap 对 merge 方法的实现

@Override
public V merge(K key, V value,
               BiFunction<? super V, ? super V, ? extends V> remappingFunction)
 
{
    if (value == null || remappingFunction == null)
        thrownew NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) != null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while ((e = e.next) != null);
        }
    }
    if (old != null) {
        V v;
        if (old.value != null) {
            int mc = modCount;
            v = remappingFunction.apply(old.value, value);
            if (mc != modCount) {
                thrownew ConcurrentModificationException();
            }
        } else {
            v = value;
        }
        if (v != null) {
            old.value = v;
            afterNodeAccess(old);
        }
        else
            removeNode(hash, key, nullfalsetrue);
        return v;
    } else {
        if (t != null)
            t.putTreeVal(this, tab, hash, key, value);
        else {
            tab[i] = newNode(hash, key, value, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
        return value;
    }
}

代码比较长,但是实现的效果比较容易描述:这个方法接收 3 个参数:keyvaluefunction

  • 如果 key 存在,将 value 按照 function 做 1 次计算后,更新到 Map 中
  • 如果 key 不存在,将 key-value 放入 Map 中

这个方法在某些场景中挺好用的,代码简洁易懂,例如:我们有 1 个 List,要统计 List 中每个元素出现的次数。我们要实现的逻辑是,遍历 List 中的每个元素,如果这个元素在 Map 中存在,Map 中的值+1;如果不存在,则放入 Map 中,次数(值)为 1。

private static void testMerge() {
    Map<String, Integer> cntMap = new HashMap<>(8);
    List<String> list = Arrays.asList("apple""orange""banana""orange");

    // 老写法
    for (String item : list) {
        if (cntMap.containsKey(item)) {
            cntMap.put(item, cntMap.get(item) + 1);
        } else {
            cntMap.put(item, 1);
        }
    }

    // 新写法
    for (String item : list) {
        cntMap.merge(item, 1, Integer::sum);
    }
}

可以看到我们使用 merge 方法的话,只用 1 行就简洁实现了这个逻辑。

putIfAbsent 

也是一个见名知意的方法:不存在 key 或者值为 null 时,才将键值对放入 Map。跟put方法相比,这个方法不会直接覆盖已有的值,在不允许覆盖旧值的场景使用起来会比较简洁。

private static void testPutIfAbsent() {
    Map<String, Integer> scoreMap = new HashMap<>(4);
    scoreMap.put("Jim"88);
    scoreMap.put("Lily"90);

    // 老写法
    if (!scoreMap.containsKey("Lily")) {
        scoreMap.put("Lily"98);
    }

    // 新写法
    scoreMap.putIfAbsent("Lily"98);
}

computer 

computer 方法需要传入 2 个参数:keyfunction。主要有 3 步操作

  1. 获取到 key 对应的 oldValue,可能为 null
  2. 经过 function 计算获取 newValue
  3. put(key, newValue)

还是以刚刚统计单次次数需求为例,看一下 computer 的写法:

private static void testComputer() {
    Map<String, Integer> cntMap = new HashMap<>(8);
    List<String> list = Arrays.asList("apple""orange""banana""orange");

    // 老写法
    for (String item : list) {
        if (cntMap.containsKey(item)) {
            cntMap.put(item, cntMap.get(item) + 1);
        } else {
            cntMap.put(item, 1);
        }
    }

    // 新写法
    for (String item : list) {
        cntMap.compute(item, (k, v) -> {
            if (v == null) {
                v = 1;
            } else {
                v += 1;
            }
            return v;
        });
    }
}

computeIfAbsent 

看名字就知道是compute方法衍生出来的方法,这个方法只在 key 不存在的时候,执行 computer 计算,如果说 key 对应的 value 存在,就直接返回这个 value。
例如,我们需要计算斐波那锲数列的时候,可以使用这个方法来简化代码:

private static void testComputerIfAbsent() {
    Map<Integer, Integer> fabMap = new ConcurrentHashMap<>(16);
    fabMap.put(01);
    fabMap.put(11);
    System.out.println(fab(5, fabMap));
}

private static Integer fab(Integer index, Map<Integer, Integer> fabMap) {
    return fabMap.computeIfAbsent(index, i -> fab(i - 2, fabMap) + fab(i - 1, fabMap));
}

computeIfPresent 

这个是computeIfAbsent的姊妹方法,区别在于,这个方法是只有 key 存在的时候,才去执行 computer 计算和值的更新。

replace 

这个方法的效果是:

  • 如果 key 存在,则更新值
  • 如果 key 不存在,什么也不做

总结

可以看到,这些 JDK8 引入的 Map 的方法,都可以在某些特定场景下简化我们的代码,虽然不嫌麻烦的话,putget等方法都可以搞定,让我想起一张远古的图

只会put、get?快来学Map这几个“新”方法

不过在不同的场景使用不同的方法,尽量把代码写的简洁和优雅,才是一个程序猿不断追求的目标吧。

看到这里了,点个赞再走呗

只会put、get?快来学Map这几个“新”方法

来源:https://juejin.cn/post/7342420969879093299

作者:podongfeng


原文始发于微信公众号(Java驿站):只会put、get?快来学Map这几个“新”方法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/312871.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!