浅析Java8 中 Map 接口的新方法

我们提一个需求:给定一个 List<String>

,统计每个元素出现的所有位置。

比如,给定 list: ["a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g"] ,那么应该返回:

a : [0] b : [1, 2] c : [3, 4, 5] d : [6, 7, 8] f : [9, 10] g : [11]

很明显,我们很适合使用 Map 来完成这件事情:

public static Map<String, List<Integer>> getElementPositions(List<String> list) {
  Map<String, List<Integer>> positionsMap = new HashMap<>();
  for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    List<Integer> positions = positionsMap.get(str);
    if (positions == null) { // 如果 positionsMap 还不存在 str 这个键及其对应的 List<Integer>
      positions = new ArrayList<>(1);
      positionsMap.put(str, positions); // 将 str 及其对应的 positions 放入 positionsMap
    }
    positions.add(i); // 将索引加入 str 相关联的 List<Integer> 中
  }
  return positionsMap;
}
public static void main(String[] args) throws Exception {
  List<String> list = Arrays.asList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
  System.out.println("使用 Java8 之前的 API:");
  Map<String, List<Integer>> elementPositions = getElementPositions(list);
  System.out.println(elementPositions);
}

运行结果:

Java8 时, Map<K, V> 接口添加了一个新的方法, putIfAbsent(K key, V value) ,功能是: 如果当前 Map 不存在键 key 或者该 key 关联的值为 null ,那么就执行 put(key, value) ;否则,便不执行 put 操作。该方法等价于如下代码:

(题外话: putIfAbsent 方法与 put 方法一样,返回的是方法调用之前与参数 key 相关联的 value )

使用 putIfAbsent 修改 getElementPositions 方法:

public static Map<String, List<Integer>> getElementPositions(List<String> list) {
  Map<String, List<Integer>> positionsMap = new HashMap<>();
  for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    positionsMap.putIfAbsent(str, new ArrayList<>(1)); // 如果 positionsMap 不存在键 str 或者 str 关联的 List<Integer> 为 null,那么就会进行 put;否则不执行 put
    positionsMap.get(str).add(i);
  }
  return positionsMap;
}
public static void main(String[] args) throws Exception {
  List<String> list = Arrays.asList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
  System.out.println("使用 putIfAbsent:");
  Map<String, List<Integer>> elementPositions = getElementPositions(list);
  System.out.println(elementPositions);
}

运行结果:

可以看到使用 putIfAbsent 之后的 getElementPositions 简洁了一点,那还能更简洁吗?

查看 Map 接口的方法,可以发现在 JDK1.8 时,还添加了如下两个方法:

查看 compute 方法的 API 文档,可以发现 compute 方法与如下代码等价

V oldValue = map.get(key);
 V newValue = remappingFunction.apply(key, oldValue);
 if (oldValue != null ) {
  if (newValue != null)
    map.put(key, newValue);
  else
    map.remove(key);
 } else { // 即 原来的 key 不存在 Map 中或该 key 关联的 value 为 null
  if (newValue != null)
    map.put(key, newValue);
  else
    return null;
 }

compute 方法和原来 put 方法的区别在于:

put(K key, V value) 方法,如果 key 在 Map 中不存在,那么直接加入;如果已经存在,那么使用新的 value 替换旧的 value ;

compute(K key, BiFunction remappingFunction) 方法可以通过一个 BiFunction 来计算出新的 value , BiFunction 的参数为旧的 key 和 value ,返回计算出新的 value —— 与 put 方法不同, compute 方法返回的会是最新的与 key 相关联的 value ,而不是旧的 value 。 所以可以使用 compute 方法改写 getElementPositions 如下:

public static Map<String, List<Integer>> getElementPositions(List<String> list) {
  Map<String, List<Integer>> positionsMap = new HashMap<>();
  for (int i = 0; i < list.size(); i++) {
    positionsMap.compute(list.get(i), (k, v) -> v == null ? new ArrayList<>(1) : v).add(i);
  }
  return positionsMap;
}
public static void main(String[] args) throws Exception {
  List<String> list = Arrays.asList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
  System.out.println("使用 compute:");
  Map<String, List<Integer>> elementPositions = getElementPositions(list);
  System.out.println(elementPositions);
}

(k, v) -> v == null ? new ArrayList<>(1) : v 即 如果当前的 value 为 null ,那么 该 BiFunction 的返回值为 new ArrayList<>(1) ;如果不为 null ,那么返回值便是本身。而且因为 compute 方法会返回新的 value —— 此时便是与 list.get(i) (key) 相关联的 ArrayList —— 所以我们可以直接调用其 add 方法。

运行结果:

很棒~ 还能更简洁吗? 我们再看看 computeIfAbsent 方法: computeIfAbsent 和 compute 的关系,就类似于 putIfAbsent 和 put 的关系: computeIfAbsent 在 key 不在 Map 中或者与 key 相关联的 value 为 null 时,才执行通过函数计算新 value 的操作,否则不执行; computeIfAbsent 的返回值也是与 key 相关联的最新的 value 。其默认实现如下:

与 compute 不同, computeIfAbsent 接受的函数操作是 Function 而不是 BiFunction —— 这很好理解, computeIfAbsent 只在 ke y 不在 Map 中或者与 key 相关联的 value 为 null 时才执行函数操作,那么显然此时与 key 相关的 value 为 null ,所以 computeIfAbsent 只接受 Function 作为参数即可 —— 该 Function 可以使用 key 作为参数计算出新的 value 。使用 computeIfAbsent 改写 getElementPositions :

public static Map<String, List<Integer>> getElementPositions(List<String> list) {
  Map<String, List<Integer>> positionsMap = new HashMap<>();

  for (int i = 0; i < list.size(); i++) {
    positionsMap.computeIfAbsent(list.get(i), k -> new ArrayList<>(1)).add(i);
  }
  return positionsMap;
}
public static void main(String[] args) throws Exception {
  List<String> list = Arrays.asList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
  System.out.println("使用 computeIfAbsent:");
  Map<String, List<Integer>> elementPositions = getElementPositions(list);
  System.out.println(elementPositions);
}

运行结果:

事实上,本文使用 putIfAbsent 时是存在问题的, positionsMap.putIfAbsent(str, new ArrayList<>(1)) ; 这句代码每次调用时都会产生一个临时的 ArrayList —— 当遍历的 List<String> 较大时,这可能会带来一定的负面影响;相比之下 compute 和 computeIfAbsent 的好处在于,它们接受的参数为函数,只会在必要时才使用函数进行计算得出新 value 。在本文类似需求的情况下,就适用性和简洁性而言, computeIfAbsent 要优于 compute 。在 JDK1.8 的 API 文档中,也说到在需要生成一个类似于 Map<K, Collection<V>> 的结构时, computeIfAbsent 很适合这种情况:

那 compute 方法适用于什么情况呢?从前面的介绍可知, compute 方法更适用于更新 key 关联的 value 时,新值依赖于旧值的情况 —— 比如统计一个 List<String> 中每个元素出现的次数:

public static Map<String, Integer> getElementCounts(List<String> list) {
  Map<String, Integer> countsMap = new HashMap<>();
  list.forEach(str -> countsMap.compute(str, (k, v) -> v == null ? 1 : v + 1)); // 此时:新值 = 旧值 + 1
  return countsMap;
}
public static void main(String[] args) throws Exception {
  List<String> list = Arrays.asList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
  System.out.println("使用 compute 计算元素出现的次数:");
  Map<String, Integer> counts = getElementCounts(list);
  System.out.println(counts);
}

运行结果:

Java8 中还为 Map 添加了一些其他方便于编码的新方法,请有兴趣的读者继续发掘。

总结

以上所述是小编给大家介绍的Java8 中 Map 接口的新方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • 浅析Java8新特性Lambda表达式和函数式接口

    什么是Lambda表达式,java8为什么使用Lambda表达式? "Lambda 表达式"(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数.我们可以把 Lambda表达式理解为是 一段可以传递的代码.最直观的是使用Lambda表达式之后不用再写大量的匿名内部类,简化代码,提高了代码的可读性. // 启动一个线程,不使用Lambda

  • 基于Java8 函数式接口理解及测试

    1. 函数式接口的理解 根据重构的思想,需要把容易变化的模块进行抽象并封装起来,从这个点来看,Java8新引入的函数式接口就是基于这个思想进行设计的. 2. 函数式接口定义 2.1 自定义如下 需要FunctionalInterface关键字显示声明: @FunctionalInterface public interface AppleInterface { public void test(); } 2.2 系统预定义 java.util.function.Consumer; java.ut

  • 30分钟入门Java8之默认方法和静态接口方法学习

    前言 上一篇文章30分钟入门Java8之lambda表达式,我们学习了lambda表达式.现在继续Java8新语言特性的学习,今天,我们要学习的是默认方法和静态接口方法. 这一Java8的新语言特性,在Android N中也得到了支持.至于如何在Android开发中配置Java8的开发环境,请查看上一篇文章30分钟入门Java8之lambda表达式. 默认方法 默认方法让我们能给我们的软件库的接口增加新的方法,并且能保证对使用这个接口的老版本代码的兼容性. 下面通过一个简单的例子来深入理解下默认

  • 实例详解Java8函数式接口

    以下我们继续深入Java8函数式编程模型 public class Test1 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.prin

  • Java8中新特性Optional、接口中默认方法和静态方法详解

    前言 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性. Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习.下面本文就给大家详细介绍了Java8中新特性Optional.接口中默认方法和静态方法的相关内容,话不多说了,来一起看看详细的介绍吧. Optional Optional 类(java.util.Optional) 是一个

  • Java8简单了解Lambda表达式与函数式接口

    Java8被称作Java史上变化最大的一个版本.其中包含很多重要的新特性,最核心的就是增加了Lambda表达式和StreamAPI.这两者也可以结合在一起使用.首先来看下什么是Lambda表达式. 使用Lambda表达式不仅让代码变的简单.而且可读.最重要的是代码量也随之减少很多.然而,在某种程度上,这些功能在Scala等这些JVM语言里已经被广泛使用. 并不奇怪,Scala社区是难以置信的,因为许多Java 8里的内容看起来就像是从Scala里搬过来的.在某种程度上,Java 8的语法要比Sc

  • Java8接口的默认方法

    Java8接口的默认方法 什么是默认方法,为什么要有默认方法? 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法.只需在方法名前面加个default关键字即可. 为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现.然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有

  • java比较器Comparable接口与Comaprator接口的深入分析

    java的比较器有两类,分别是Comparable接口和Comparator接口.在为对象数组进行排序时,比较器的作用非常明显,首先来讲解Comparable接口.让需要进行排序的对象实现Comparable接口,重写其中的compareTo(T o)方法,在其中定义排序规则,那么就可以直接调用java.util.Arrays.sort()来排序对象数组,实例如下: 复制代码 代码如下: class Student implements Comparable<Student>{    priv

  • Java中Map集合(接口)的基本方法程序演示

    本文实例为大家分享了Java中Map集合的基本方法程序演示的具体代码,供大家参考,具体内容如下 package pack02; import java.util.*; public class MapDemo { public static void main(String[] args) { //定义一个Map接口类型的引用,指向HashMap类型的对象 Map<String,String> ma = new HashMap<String, String>(); ma.put(&

  • 浅析Java8 中 Map 接口的新方法

    我们提一个需求:给定一个 List<String> ,统计每个元素出现的所有位置. 比如,给定 list: ["a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g"] ,那么应该返回: a : [

  • 浅谈java8中map的新方法--replace

    Map在Java8中新增了两个replace的方法 1.replace(k,v) 在指定的键已经存在并且有与之相关的映射值时才会将指定的键映射到指定的值(新值) 在指定的键不存在时,方法会return回来一个null javadoc的注释解释了该默认值方法的实现的等价Java代码: if (map.containsKey(key)) { return map.put(key, value); } else { return null; } 下面展示的是新方法和JDK8之前的方法比较: /* *

  • java8中Map的一些骚操作总结

    一 前言 本篇内容是关于 map 新特性的一些方法使用上的介绍,如果有不足之处欢迎补充!! 二 map新特性 关于以下函数式编程的函数的计算知识追寻者都使用 简单字符串代替了,参数无非就是Key,value: 2.1 forEach forEach迭代,相当于for循环 public static void main(String[] args) { HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put(&qu

  • 关于Java8中map()和flatMap()的一些事

    两个方法的背景 这两个方法看起来做着同样的事情,但实际上又有些不一样.看源码部分是这样的 package java.util.stream; map()方法 /** * @param <R> The element type of the new stream * @param mapper a <a href="package-summary.html#NonInterference" rel="external nofollow" rel=&q

  • Java中Map接口使用以及有关集合的面试知识点汇总

    目录 Map接口 存储特点 常用实现类 创建方法 常用方法 遍历方法 不同实现类的使用 集合面试知识点补充 结语 Map接口 存储特点 以键(key)值(value)对的形式存储 键无序.无下标.元素不可重复 值无序.无下标.元素可以重复 常用实现类 HashMapJDK1.2 底层哈希表实现 线程不安全,效率高 LinkedHashMapJDK1.2 是HashMap的子类,底层哈希表实现 线程不安全,效率高 TreeMapJDK1.2 是SortedMap的实现类,底层红黑树实现 线程不安全

  • Golang中map数据类型的使用方法

    目录 前言 案例 map map定义 map声明 map的操作 总结 前言 今天咱们来学习一下golang中的map数据类型,单纯的总结一下基本语法和使用场景,也不具体深入底层.map类型是什么呢?做过PHP的,对于数组这种数据类型是一点也不陌生了.PHP中的数组分为索引数组和关联数组.例如下面的代码: // 索引数组[数组的key是一个数字, 从0,1,2开始递增] $array = [1, '张三', 12]; // 关联数组[数组的key是一个字符串,可以自定义key的名称] $array

  • 初步解读Golang中的接口相关编写方法

    概述 如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键.在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有数据结构的核心. Go语言中的接口是一些方法的集合(method set),它指定了对象的行为:如果它(任何数据类型)可以做这些事情,那么它就可以在这里使用. 接口的定义和使用 比如 复制代码 代码如下: type I interface{     Get() int     Put(int)   } 这段话就定

  • JS自定义对象实现Java中Map对象功能的方法

    本文实例讲述了JS自定义对象实现Java中Map对象功能的方法.分享给大家供大家参考.具体分析如下: Java中有集合,Map等对象存储工具类,这些对象使用简易,但是在JavaScript中,你只能使用Array对象. 这里我创建一个自定义对象,这个对象内包含一个数组来存储数据,数据对象是一个Key,可以实际存储的内容!   这里Key,你要使用String类型,和Java一样,你可以进行一些增加,删除,修改,获得的操作. 使用很简单,我先把工具类给大家看下: 复制代码 代码如下: /**  *

  • Vue中遍历数组的新方法实例详解

    1.foreach foreach循环对不能使用return来停止循环 search(keyword){ var newList = [] this.urls.forEach(item =>{ if(item.name.indexOf(keyword) != -1){ newList.push(item) } }) return newList } 2.filter item对象就是遍历数组中的一个元素,includes是es6中的新方法,在search方法中直接返回新数组 search(key

  • Java8中Stream的详细使用方法大全

    目录 一.概述 1.使用流的好处 2.流是什么? 二.分类 三.Stream的创建 1.通过 java.util.Collection.stream() 方法用集合创建流 2.使用 java.util.Arrays.stream(T[]array)方法用数组创建流 3.使用 Stream的静态方法:of().iterate().generate() 四.Stream API简介 1.遍历/匹配(foreach/find/match) 2.按条件匹配filter 3.聚合max.min.count

随机推荐