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的技術微信公眾號唄,最新的技術文章、技術課程以及技術專欄將會第一時間在公眾號發布哦!