深度掌握java stream 流操作,让你的代码高出一个逼格

简介:   一、介绍  我们都知道,从 Java8 开始,jdk 新增加了一个 Stream 类,用来补充集合类,它的强大,相信用过它的朋友,能明显地感受到,不用使用for循环就能对集合作出很好的操作。  Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

  一、介绍

  我们都知道,从 Java8 开始,jdk 新增加了一个 Stream 类,用来补充集合类,它的强大,相信用过它的朋友,能明显地感受到,不用使用for循环就能对集合作出很好的操作。

  Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

  这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

  元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到全面处理的结果。

  操作流程如下:

  +--------------------+ +------+ +------+ +---+ +-------+

  | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|

  +--------------------+ +------+ +------+ +---+ +-------+

  采用 Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

  下面,我们就以实际的日常开发编程风格做对比,学习完 Stream 的编程风格之后,我敢保证,你会爱上它!

  二、遍历操作2.1、遍历集合

  日常开发中,我们经常需要需要遍历集合对象中的元素,例如,我们会采用如下方式进行遍历元素,然后过滤出某个字段的集合,jdk7 的操作:

  /**

  * jdk7 从集合对象中获取用户ID集合

  * @param userList

  * @return

  */

  public List getUserIds(List userList){

  List userIds = new ArrayList<>();

  for (User user : userList) {

  userIds.add(user.getUserId());

  }

  return userIds;

  }

  当采用 Stream 编程之后,只需要通过一行代码,即可实现:

  /**

  * jdk8 从集合对象中获取用户ID集合

  * @param userList

  * @return

  */

  public List getUserIds(List userList){

  List userIds = userList.stream().map(User::getUserId).collect(Collectors.toList());

  return userIds;

  }

  2.2、筛选元素

  筛选元素,是日常开发中经常会碰到,例如在 jdk7,我们会这么操作:

  /**

  * jdk7 从集合对象中过滤出用户ID不为空的数据

  * @param userList

  * @return

  */

  public List getUserIds7(List userList){

  List userIds = new ArrayList<>();

  for (User user : userList) {

  if(user.getUserId() != null){

  userIds.add(user.getUserId());

  }

  }

  return userIds;

  }

  采用 Stream api,我们只需要通过filter方法来筛选出需要的数据,即可过滤出用户ID不为空的数据。

  /**

  * jdk8 从集合对象中筛选出用户ID不为空的数据

  * @param userList

  * @return

  */

  public List getUserIds8(List userList){

  List userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toList());

  return userIds;

  }

  2.3、删除重复的内容

  如果你想对返回的集合内容排除重复的数据,操作也很简单,在合并的时候使用Collectors.toSet()即可!

  /**

  * jdk8 从集合对象中筛选出用户ID不为空的数据,并进行去重

  * @param userList

  * @return

  */

  public Set getUserIds(List userList){

  Set userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toSet());

  return userIds;

  }

  2.4、数据类型转换

  在实际的开发过程中,经常会出现数据类型定义不一致的问题,例如有的系统,使用String接受,有的是用Long,对于这种场景,我们需要将其转换,操作也很简单

  /**

  * jdk8 将Long类型数据转换成String类型

  * @param userIds

  * @return

  */

  public List getUserIds10(List userIds){

  List userIdStrs = userIds.stream().map(x -> x.toString()).collect(Collectors.toList());

  return userIdStrs;

  }

  2.5、数组转集合

  我们还会碰到,前端传给我们的是一个数组,但是我们需要转成集合,采用 stream api 操作也很简单!

  public static void main(String[] args) {

  //创建一个字符串数组

  String[] strArray = new String[]{"a","b","c"};

  //转换后的List 属于 java.util.ArrayList 能进行正常的增删查操作

  List strList = Stream.of(strArray).collect(Collectors.toList());

  }

  三、集合转Map操作

  在实际的开发过程中,还有一个使用最频繁的操作就是,将集合元素中某个主键字段作为key,元素作为value,来实现集合转map的需求,这种需求在数据组装方面使用的非常多,尤其是在禁止连表 sql 查询操作的公司,视图数据的拼装只能在代码层面来实现。

  例如下面这段代码,角色表里面关联角色组ID信息,当查询角色信息的时候,需要把角色组名称也展示处理,采用map方式来匹配,效率会非常高。

  实际代码案例分享

  //角色组ID集合

  Set roleGroupIds = new HashSet<>();

  //查询所有的角色信息

  List dbList = roleInfoMapper.findByPage(request);

  for (RoleInfo source : dbList) {

  roleGroupIds.add(source.getRoleGroupId());

  RoleInfoDto result = new RoleInfoDto();

  BeanUtils.copyProperties(source, result);

  resultList.add(result);

  }

  //查询角色组信息

  if (CollectionUtils.isNotEmpty(roleGroupIds)) {

  List roleGroupInfoList = roleGroupInfoMapper.selectByIds(new ArrayList<>(roleGroupIds));

  if (CollectionUtils.isNotEmpty(roleGroupInfoList)) {

  //将List转换成Map,其中id主键作为key,对象作为value

  Map sourceMap = new HashMap<>();

  for (RoleGroupInfo roleGroupInfo : roleGroupInfoList) {

  sourceMap.put(roleGroupInfo.getId(), roleGroupInfo);

  }

  //封装角色组名称

  for (RoleInfoDto result : resultList) {

  if (sourceMap.containsKey(result.getRoleGroupId())) {

  result.setRoleGroupName(sourceMap.get(result.getRoleGroupId()).getName());

  }

  }

  }

  }

  3.1、集合转 map(不分组)

  在 jdk7 中,将集合中的元素转 map,我们通常会采用如下方式。

  /**

  * jdk7 将集合转换成Map,其中用户ID作为主键key

  * @param userList

  * @return

  */

  public Map getMap(List userList){

  Map userMap = new HashMap<>();

  for (User user : userList) {

  userMap.put(user.getUserId(), user);

  }

  return userMap;

  }

  在 jdk8 中,采用 stream api的方式,我们只需要一行代码即可实现

  /**

  * jdk8 将集合转换成Map,其中用户ID作为主键key,如果集合对象有重复的key,以第一个匹配到的为主

  * @param userList

  * @return

  */

  public Map getMap(List userList){

  Map userMap = userList.stream().collect(Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1));

  return userMap;

  }

  打开Collectors.toMap方法源码,一起来看看到底是啥。

  public static

  Collector> toMap(Function keyMapper,

  Function valueMapper,

  BinaryOperator mergeFunction) {

  return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);

  }

  从参数表可以看出:

  第一个参数:表示 key第二个参数:表示 value第三个参数:表示某种规则

  上文中的Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1),表达的意思就是将userId的内容作为key,v -> v是表示将元素user作为value,其中(k1,k2) -> k1表示如果存在相同的key,将第一个匹配的元素作为内容,第二个舍弃!

  3.2、集合转map(分组)

  在实际的操作中,有一些场景需要我们将相同的key,加入到一个集合,而不是覆盖,那该如何做呢?

  如果是采用 jdk7,我们大概会这么做。

  /**

  * jdk7 将集合转换成Map,将相同的key,加入到一个集合中,实现分组

  * @param userList

  * @return

  */

  public Map> getMapGroup(List userList){

  Map> userListMap = new HashMap<>();

  for (User user : userList) {

  if(userListMap.containsKey(user.getUserId())){

  userListMap.get(user.getUserId()).add(user);

  } else {

  List users = new ArrayList<>();

  users.add(user);

  userListMap.put(user.getUserId(), users);

  }

  }

  return userListMap;

  }

  而在 jdk8 中,采用 stream api的方式,我们只需要一行代码即可实现

  /**

  * jdk8 将集合转换成Map,将相同的key,加入到一个集合中,实现分组

  * @param userList

  * @return

  */

  public Map> getMapGroup(List userList){

  Map> userMap = userList.stream().collect(CollectorsingBy(User::getUserId));

  return userMap;

  }

  四、分页操作

  stream api 的强大之处还不仅仅是对集合进行各种组合操作,还支持分页操作。

  例如,将如下的数组从小到大进行排序,排序完成之后,从二手手机靓号转让第1行开始,查询10条数据出来,操作如下:

  //需要查询的数据

  List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5,10, 6, 20, 30, 40, 50, 60, 100);

  List dataList= numbers.stream().sorted((x, y) -> xpareTo(y)).skip(0).limit(10).collect(Collectors.toList());

  System.out.println(dataList.toString());

  其中skip参数表示第几行,limit表示查询的数量,类似页容量!

  五、查找与匹配操作

  stream api 还支持对集合进行查找,同时还支持正则匹配模式。

  allMatch(检查是否匹配所有元素)

  List list = Arrays.asList(10, 5, 7, 3);

  boolean allMatch = list.stream()//

  .allMatch(x -> x > 2);//是否全部元素都大于2

  System.out.println(allMatch);

  findFirst(返回第一个元素)

  List list = Arrays.asList(10, 5, 7, 3);

  Optional first = list.stream()//

  .findFirst();

  Integer val = first.get();

  System.out.println(val);//输出10

  reduce(可以将流中元素反复结合起来,得到一个值)

  List list = Arrays.asList(10, 5, 7, 3);

  Integer result = list.stream()//

  uce(2, Integer::sum);

  System.out.println(result);//输出27,其实相当于2+10+5+7+3,就是一个累加

  stream api 支持的操作方法非常多,这里只列举了几种类型,具体在使用的时候,可以参考官网接口文档说明!

  六、并行操作

  所谓并行,指的是多个任务在同一时间点发生,并由不同的cpu进行处理,不互相抢占资源;而并发,指的是多个任务在同一时间点内同时发生了,但由同一个cpu进行处理,互相抢占资源。

  这点上大家一定要区分清楚,别弄混了!

  stream api 的并行操作和串行操作,只有一个方法区别,其他都一样,例如下面我们使用parallelStream来输出空字符串的数量:

  List strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");

  // 采用并行计算方法,获取空字符串的数量

  long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

  在实际使用的时候,并行操作不一定比串行操作快!对于简单操作,数量非常大,同时服务器是多核的话,建议使用Stream并行!反之,采用串行操作更可靠!

  七、小结

  本文主要,围绕 jdk stream api 操作,结合实际的日常开发需求,做了简单总结和分享,鉴于笔者才疏学浅,可能也有理解不到位的地方,欢迎网友们批评指出!

目录
相关文章
|
12天前
|
Java 测试技术 应用服务中间件
常见 Java 代码缺陷及规避方式(下)
常见 Java 代码缺陷及规避方式(下)
38 0
|
14天前
|
Java
Java中ReentrantLock释放锁代码解析
Java中ReentrantLock释放锁代码解析
25 8
|
8天前
|
存储 安全 Java
说说Java 8 引入的Stream API
说说Java 8 引入的Stream API
12 0
|
9天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。
|
12天前
|
Java
代码的魔法师:Java反射工厂模式详解
代码的魔法师:Java反射工厂模式详解
26 0
|
12天前
|
前端开发 Oracle Java
Java 22 新增利器: 使用 Java Stream Gather 优雅地处理流中的状态
Java 22 新增利器: 使用 Java Stream Gather 优雅地处理流中的状态
23 0
|
12天前
|
监控 安全 Java
常见 Java 代码缺陷及规避方式(中)
常见 Java 代码缺陷及规避方式(中)
25 1
|
12天前
|
存储 Java 关系型数据库
掌握Java 8 Stream API的艺术:详解流式编程(一)
掌握Java 8 Stream API的艺术:详解流式编程
46 1
|
14天前
|
设计模式 算法 Java
23种设计模式,模板方法模式的概念优缺点以及JAVA代码举例
【4月更文挑战第10天】模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
15 0
|
15天前
|
设计模式 Java
23种设计模式,状态模式的概念优缺点以及JAVA代码举例
【4月更文挑战第9天】状态模式是一种行为设计模式,允许一个对象在其内部状态改变时改变它的行为,这个对象看起来似乎修改了它的类。
28 4