SpringBoot系列(七):Java8的Stream API,让集合操作更为高效


作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

摘要:本文我们将开个小插曲,分享介绍如何基于Java8提供的Stream特性,高效操作我们的集合,如List、Set、Map等等。其中,将主要介绍Stream特性提供的筛选过滤功能Filter、对象转化功能Map、去重Distinct、排序Sorted、最小值Min以及最大值Max等核心操作。

内容:了解过jdk动态的小伙伴们估计都知道,jdk已经出到12的版本了(是不是有点怀疑目前仍然还在使用jdk1.6、jdk1.5的自己),当然啦,我们都知道,不管jdk怎么升级,其底层核心数据库以及jvm的特性是变化不大的,特别是java8之后的版本,其主要还是以java8作为奠基进行一步步扩张的。如下图所示:


Java8对外提供的特性有很多,Stream便是其中的一大功能特性,除此之外,Java8还提供了接口默认方法、Lambda表达式、函数式接口等等。说起Java8的Stream,其在集合的操作中着实发挥了强大的作用,别的不说,其中的简洁、高效就特别令人眼前一亮。

Stream作为Java8的新特性,主要是基于lambda表达式,是对集合对象功能的增强,它专注于对集合对象进行各种高效、便利的聚合操作或者大批量的数据操作,可以提高编程的效率和代码可读性。

对于Stream的原理,正是其字面上的含义,Stream是将等待被处理的元素看做一种流,而流在管道中传输,并且可以在管道的节点上执行相应的处理操作,包括过滤筛选、去重、排序、聚合等等,元素流在管道中经过中间操作的处理,最后由最终操作如collect等方法得到前面处理的结果。

对于Stream的原理,各位小伙伴也可以网上搜索相应的文章进行深入的研究,在这里我们只做简单性的介绍。在后续的代码实战中,各位小伙伴可以看到上述对于Stream的原理将很好地在代码中得到体现。

值得一提的是,Stream API在执行的具体操作之前,需要先生成“流”,主要有两种方式:

(1)stream() − 为集合创建串行流

(2)parallelStream() - 为集合创建并行流

下面,我们在com.debug.springboot.server.dto目录下新建一个实体类PersonDto,用于后续的Stream代码实战中,其源代码如下所示:

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class PersonDto implements Serializable{
private Integer id;

private Integer age;

private String name;

}

其中,我们需要在该实体类中加入Equals()和HashCode()方法,用于后续执行Stream的去重API做铺垫!

另外,也需要在com.debug.springboot.server.utils 包目录下新建一个Java8Util类,用于介绍一些比较突出的Steam API操作。

首先,我们需要在Java8Util类中新建一个可以被共用的对象集合,并采用static静态代码块进行初始化,如下所示:

private static List<PersonDto> list;
//初始化对象集合
static {
list= Lists.newLinkedList();
PersonDto dto1=new PersonDto(1,21,"赵六");
PersonDto dto2=new PersonDto(2,20,"张三");
PersonDto dto3=new PersonDto(3,22,"李四");
PersonDto dto4=new PersonDto(4,20,"王五");

list.add(dto1);
list.add(dto2);
list.add(dto3);
list.add(dto4);
}

1、首先介绍Stream的筛选过滤功能Filter:下面的代码用于筛选过滤出年龄>20的对象集合,筛选过滤出名字为 王五 的对象信息,如下所示:  

//筛选功能:filter 结合 collect方法 使用
public static void method1(){
//筛选年龄大于20的小伙子
if (list!=null && !list.isEmpty()){
System.out.println("\n--filter筛选功能1,结果:");

List<PersonDto> resList=list.stream().filter(p -> p.getAge() > 20).collect(Collectors.toList());
System.out.println(resList);
}

//筛选名字为 王五 的小伙子
if (list!=null && !list.isEmpty()){
System.out.println("\n--filter筛选功能2,结果:");

List<PersonDto> resList=list.stream().filter(p -> "王五".equals(p.getName())).collect(Collectors.toList());
System.out.println(resList);
}
}

之后,我们可以写一个main方法,直接调用method1(),其运行结果如下图所示:  



2、紧接着,我们介绍Stream中的转换功能Map,如有以下的业务场景:当得到一个对象集合之后,要求我们需要抽取出每个对象中的某个字段,组成新的列表,比如抽取这个对象集合中的Id,或者Name,从而组成新的一个列表,源代码如下所示:  

//转换map~按照指定的字段/元素属性进行转换:结合 collect 方法使用
public static void method2(){
if (list!=null && !list.isEmpty()){
System.out.println("\n--转换map~按照指定的字段/元素属性进行转换,结果:");

Set<String> nameSet=list.stream().map(PersonDto::getName).collect(Collectors.toSet());
System.out.println(nameSet);
}
}

运行结果如下图所示:  


3、然后,我们介绍一下“对象去重Distinct”的操作,这一操作,我们需要在实体类中加入 @EqualsAndHashCode 的lombok注解,源代码如下所示:  

//去重distinct ~ 配合对象的 equals() 和 hashCode()方法
public static void method3(){
//TODO:对象去重 - 对象需要实现equals hashCode方法
list.add(new PersonDto(4,20,"王五"));
System.out.println("去重以前:"+list);
List<PersonDto> resList=list.stream().distinct().collect(Collectors.toList());
System.out.println("去重以后:"+resList);
}

运行结果如下图所示:  


4、顺道,我们介绍一下Stream提供的Sorted排序功能,即我们可以根据对象中的某个字段实施某种排序方式(正序、倒序)等,如下代码所示,我们可以借助Comparator比较器连续先后的对对象集合进行排序:先按照年龄排序、再按照id排序(正序、倒序),源代码如下所示:  

//排序sorted
public static void method4(){
//按照年龄排序、再按照id排序
list.add(new PersonDto(0,20,"郑六"));
if (list!=null && !list.isEmpty()){
List<PersonDto> resList=list.stream()
.sorted(Comparator.comparingInt(PersonDto::getAge).thenComparing(Comparator.comparing(PersonDto::getId)))
.collect(Collectors.toList());
System.out.println("按照年龄排序、再按照id排序-顺序:\n"+resList);
}

//按照年龄排序、再按照id排序 ~ 也可以将最终结果倒序来
if (list!=null && !list.isEmpty()){
List<PersonDto> resList=list.stream()
.sorted(Comparator.comparingInt(PersonDto::getAge).thenComparing(Comparator.comparing(PersonDto::getId)).reversed())
.collect(Collectors.toList());
System.out.println("\n按照年龄排序、再按照id排序-最终倒序:\n"+resList);
}

}

其运行结果如下图所示:  


5、最后,我们介绍一下如果根据对象中某个字段的最大、最小,从而取出整个对象集合中该最小值字段、最大值字段对应的对象记录,源码如下所示:  

//最小min、最大max
public static void method5(){
if (list!=null && !list.isEmpty()){
PersonDto p=list.stream()
.min(Comparator.comparingInt(PersonDto::getId))
.get();
System.out.println("\nid最小:"+p);
}

if (list!=null && !list.isEmpty()){
PersonDto p=list.stream()
.max(Comparator.comparingInt(PersonDto::getAge))
.get();
System.out.println("\n年龄最大:"+p);
}
}


其运行结果如下图所示:  


补充:

1、本文涉及到的相关的源代码可以到此地址,check出来进行查看学习:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已将本文所涉及的内容整理录制成视频教程,感兴趣的小伙伴可以前往观看学习:

https://www.fightjava.com/web/index/course/detail/5

3、关注一下Debug的技术微信公众号呗,最新的技术文章、技术课程以及技术专栏将会第一时间在公众号发布哦!