Java8?函數(shù)式編程stream流使用詳解
開(kāi)篇介紹
Java 8 中新增的特性旨在幫助程序員寫(xiě)出更好的代碼,其中對(duì)核心類庫(kù)的改進(jìn)是很關(guān)鍵的一部分,也是本章的主要內(nèi)容。對(duì)核心類庫(kù)的改進(jìn)主要包括集合類的 API 和新引入的流(Stream),流使程序員得以站在更高的抽象層次上對(duì)集合進(jìn)行操作。下面將介紹stream流的用法。
1.初始環(huán)境準(zhǔn)備
? 場(chǎng)景:現(xiàn)在有一個(gè)公司,公司部門有一級(jí)部門,二級(jí)部門甲和二級(jí)部門乙(其中二級(jí)部門甲和二級(jí)部門乙是一級(jí)部門的子部門),
一級(jí)部門下面有有001號(hào)員工小明,二級(jí)部門甲下面有002號(hào)員工小剛和003號(hào)員工小李,二級(jí)部門乙有002號(hào)員工小剛和004號(hào)員工小張,其中員工id是唯一的,員工小剛既是二級(jí)部門甲又是二級(jí)部門乙的員工。代碼展示如下:
public class LambdaUseCase {
static List<Department> departmentList;
static {
// 一級(jí)部門,部門人員有001號(hào)員工小明
Department departmentOne = new Department("一級(jí)部門", 1,10000L,11000L,
Arrays.asList(new Person("001","小明",22)));
// 二級(jí)部門甲,部門人員有002號(hào)員工小剛和003號(hào)員工小李
Department departmentTwoFirst = new Department("二級(jí)部門甲", 2,8000L,13000L,
Arrays.asList(new Person("002","小剛",23),
new Person("003","小李",32)));
// 二級(jí)部門乙,部門人員有002號(hào)員工小剛和004號(hào)員工小張
Department departmentTwoSecond = new Department("二級(jí)部門已", 2,7500L,15000L,
Arrays.asList(new Person("002","小剛",23),
new Person("004","小張",34)));
departmentList = Arrays.asList(departmentOne,departmentTwoFirst,departmentTwoSecond);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Department {
// 部門名
private String departmentName;
// 部門等級(jí)
private Integer departmenRank;
// 部門薪資(單位分)
private Long departSalary;
// 部門日盈利
private Long departProfit;
// 部門人員集合
private List<Person> persons;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//重寫(xiě)equal和hashcode方法,用于數(shù)據(jù)去重
@EqualsAndHashCode
class Person {
// 人員id
private String personId;
// 人員姓名
private String personName;
// 人員年齡
private Integer personAge;
}
2.創(chuàng)建流
單列集合: 集合對(duì)象.stream() 創(chuàng)建流
departmentList.stream();
數(shù)組:Arrays.stream(數(shù)組)或者使用Stream.of來(lái)創(chuàng)建
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
Stream.of(1,2,3,4);雙列集合:轉(zhuǎn)換成單列集合后再創(chuàng)建
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();3.中間操作
3.1 map
如果有一個(gè)函數(shù)可以將一種類型的值轉(zhuǎn)換成另外一種類型,map 操作就可以使用該函數(shù),將一個(gè)流中的值轉(zhuǎn)換成一個(gè)新的流
需求:公司今年收益提供,決定把所有部門的平均薪資提升1000。
List<Department> departments = departmentList.stream().map(e -> {
e.setDepartSalary(e.getDepartSalary() + 1000);
return e;
}).collect(Collectors.toList());3.2 filter
對(duì)流中的元素進(jìn)行過(guò)濾,篩選出符合過(guò)濾條件的數(shù)據(jù)
需求:需要篩選出部門平均薪資大于等于8000的數(shù)據(jù)。
List<Department> departments = departmentList.stream() .filter(e -> e.getDepartSalary() >= 8000) .collect(Collectors.toList());
3.3 flatMap
flatMap 方法可用 Stream 替換值,然后將多個(gè) Stream 連接成一個(gè) Stream
需求:把公司所有的人員收集到一個(gè)集合中。
不使用flatMap,你可能會(huì)這么做,循環(huán)里套循環(huán),看上去不太美觀。
Set<Person> personSet = new HashSet<>();
departmentList.stream().forEach(e->{
e.getPersons().stream().forEach(person->{
personSet.add(person);
});
});使用flatMap寫(xiě)法。
Set<Person> personSet = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.collect(Collectors.toSet());3.4 distinct
去除流中的重復(fù)元素
需求:將公司所有的人員統(tǒng)計(jì)出來(lái),這回不使用Set去重,注意:002號(hào)員工即在二級(jí)部門甲又在二級(jí)部門乙
List<Person> personSet = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.distinct()
.collect(Collectors.toList());注意:distinct方法是依賴Object的equals方法來(lái)判斷是否是相同對(duì)象的。所以需要注意重寫(xiě)equals方法。
3.5 sorted
對(duì)流中的元素進(jìn)行排序
需求: 按薪資從低到高將部門列出來(lái)。
List<Department> departments = departmentList.stream()
.sorted((o1, o2) -> o1.getDepartSalary().compareTo(o2.getDepartSalary()))
.collect(Collectors.toList());注意:如果調(diào)用空參的sorted()方法,需要流中的元素是實(shí)現(xiàn)了Comparable。
3.6 limit
可以設(shè)置流的最大長(zhǎng)度,超出的部分將被拋棄
需求:按薪資從低到高將部門列出來(lái),并且找出薪資最低的倆個(gè)部門
List<Department> departments = departmentList.stream() .sorted(Comparator.comparing(Department::getDepartSalary)) .limit(2) .collect(Collectors.toList());
3.7 skip
跳過(guò)流中的前n個(gè)元素,返回剩下的元素
需求:按薪資從低到高將部門列出來(lái),并且忽略薪資最低的部門
List<Department> departments = departmentList.stream() .sorted(Comparator.comparing(Department::getDepartSalary)) .skip(1) .collect(Collectors.toList());
4.終結(jié)操作
4.1 foreach
對(duì)流中的元素進(jìn)行遍歷操作
需求:要求輸出全部部門的名稱
departmentList.stream()
.forEach(e -> System.out.println(e.getDepartmentName()));4.2 count
可以用來(lái)獲取當(dāng)前流中元素的個(gè)數(shù)。
需求:統(tǒng)計(jì)部門的格式
long count = departmentList.stream()
.count();4.3 max和min
Stream 上常用的操作之一是求最大值和最小值
需求:分別找出部門里面薪資最高的部門和最低的部門
Department maxDepartment = departmentList.stream() .max(Comparator.comparing(Department::getDepartSalary)) .get(); Department minDepartment = departmentList.stream() .min(Comparator.comparing(Department::getDepartSalary)) .get();
4.4 collect
將當(dāng)前的流轉(zhuǎn)換為一個(gè)集合
4.4.1 Collectors.toList()
需求:獲得一個(gè)保存所有部門名字的集合
List<String> departNameList = departmentList.stream() .map(Department::getDepartmentName) .collect(Collectors.toList());
4.4.2 Collectors.toSet()
需求:獲得部門所有人的姓名(重名的忽略)
Set<String> personNameList = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.map(Person::getPersonName)
.collect(Collectors.toSet());4.4.3 Collectors.toMap(keyMapper, valueMapper)
需求:獲得部門名稱當(dāng)做key,員工當(dāng)做value的Map
Map<String, List<Person>> collect = departmentList.stream() .collect(Collectors.toMap(Department::getDepartmentName, Department::getPersons));
4.4.4 Collectors.joining()
需求:將所有的部門名字連起來(lái),并且前綴是【,后綴是】,分隔符是,
departmentList.stream()
.map(Department::getDepartmentName)
.collect(Collectors.joining(",","[","]"));4.4.5 Collectors.partitioningBy(predicate)
接受一個(gè)流,并將其分成兩部分,使用 Predicate 對(duì)象判斷一個(gè)元素應(yīng)該屬于哪個(gè)部分,并根據(jù)布爾值返回一個(gè) Map 到列表。
需求:將一級(jí)部門和其他級(jí)別的部門分離出來(lái)
Map<Boolean, List<Department>> collect = departmentList
.stream()
.collect(Collectors.partitioningBy(e -> e.getDepartmenRank() == 1));4.4.6 Collectors.groupingBy(classifier)
接受一個(gè)分類函數(shù),用來(lái)對(duì)數(shù)據(jù)分組
需求:將部門按部門的階級(jí)分組
Map<Integer, List<Department>> collect = departmentList
.stream()
.collect(Collectors.groupingBy(Department::getDepartmenRank));4.4.7 組合收集器
各種收集器已經(jīng)很強(qiáng)大了,但如果將它們組合起來(lái),會(huì)變得更強(qiáng)大。
需求:統(tǒng)計(jì)各個(gè)階級(jí)部門的平均薪資
Map<Integer, Double> collect = departmentList.stream() .collect(Collectors.groupingBy(Department::getDepartmenRank,Collectors.averagingLong(Department::getDepartSalary)));
這個(gè)例子中用到了第二個(gè)收集器,用以收集最終結(jié)果的一個(gè)子集。這些收集器叫作下游收集器。收集器是生成最終結(jié)果的一劑配方,下游收集器則是生成部分結(jié)果的配方,主收集器中會(huì)用到下游收集器。這種組合使用收集器的方式,使得它們?cè)?Stream 類庫(kù)中的作用更加強(qiáng)大。
4.5 reduce
對(duì)流中的數(shù)據(jù)按照指定的方式計(jì)算出結(jié)果
需求:計(jì)算出部門總的日盈利
Long sum = departmentList.stream() .map(Department::getDepartProfit) .reduce(0L, (result, element) -> result + element);
4.6 anyMatch
可以用來(lái)判斷是否有任意符合匹配條件的元素,結(jié)果為boolean類型。
需求:判斷部門中有沒(méi)有薪資大于9000的部門
boolean b = departmentList.stream()
.anyMatch(e -> e.getDepartSalary() > 9000L);4.7 allMatch
可以用來(lái)判斷是否都符合匹配條件,結(jié)果為boolean類型。如果都符合結(jié)果為true,否則結(jié)果為false。
需求:判斷部門薪資是不是都大于9000
boolean b = departmentList.stream()
.allMatch(e -> e.getDepartSalary() > 9000L);4.8 noneMatch
可以判斷流中的元素是否都不符合匹配條件。如果都不符合結(jié)果為true,否則結(jié)果為false
需求:判斷是不是所有的部門薪資都大于9000
boolean b = departmentList.stream()
.noneMatch(e -> e.getDepartSalary() > 9000L);4.9 findAny
獲取流中的任意一個(gè)元素。該方法沒(méi)有辦法保證獲取的一定是流中的第一個(gè)元素。
需求:找到任意一個(gè)部門
Department department = departmentList
.stream()
.findAny()
.get();4.10 findFirst
獲取流中的第一個(gè)元素。
需求:獲得薪資最低的一個(gè)部門
Department department = departmentList
.stream()
.sorted(Comparator.comparing(Department::getDepartSalary))
.findFirst()
.get();5.高級(jí)用法
5.1 對(duì)基本類型處理(mapToInt,mapToLong,mapToDouble)
在 Java 中,有一些相伴的類型,比如 int 和 Integer——前者是基本類型,后者是裝箱類型?;绢愋蛢?nèi)建在語(yǔ)言和運(yùn)行環(huán)境中,是基本的程序構(gòu)建模塊;而裝箱類型屬于普通的 Java 類,只不過(guò)是對(duì)基本類型的一種封裝。由于裝箱類型是對(duì)象,因此在內(nèi)存中存在額外開(kāi)銷。比如,整型在內(nèi)存中占用4 字節(jié),整型對(duì)象卻要占用 16 字節(jié)。這一情況在數(shù)組上更加嚴(yán)重,整型數(shù)組中的每個(gè)元素只占用基本類型的內(nèi)存,而整型對(duì)象數(shù)組中,每個(gè)元素都是內(nèi)存中的一個(gè)指針,指向 Java堆中的某個(gè)對(duì)象。在最壞的情況下,同樣大小的數(shù)組,Integer[] 要比 int[] 多占用 6 倍內(nèi)存。
將基本類型轉(zhuǎn)換為裝箱類型,稱為裝箱,反之則稱為拆箱,兩者都需要額外的計(jì)算開(kāi)銷。對(duì)于需要大量數(shù)值運(yùn)算的算法來(lái)說(shuō),裝箱和拆箱的計(jì)算開(kāi)銷,以及裝箱類型占用的額外內(nèi)存,會(huì)明顯減緩程序的運(yùn)行速度。
為了減小這些性能開(kāi)銷,Stream 類的某些方法對(duì)基本類型和裝箱類型做了區(qū)分。高階函數(shù) mapToLong 和其他類似函數(shù)即為該方面的一個(gè)嘗試。在 Java 8 中,僅對(duì)整型、長(zhǎng)整型和雙浮點(diǎn)型做了特殊處理,因?yàn)樗鼈冊(cè)跀?shù)值計(jì)算中用得最多,特殊處理后的系統(tǒng)性能提升效果最明顯。
如有可能,應(yīng)盡可能多地使用對(duì)基本類型做過(guò)特殊處理的方法,進(jìn)而改善性能。這些特殊的 Stream 還提供額外的方法,避免重復(fù)實(shí)現(xiàn)一些通用的方法,讓代碼更能體現(xiàn)出數(shù)值計(jì)算的意圖。
需求:需要計(jì)算出公司部門利潤(rùn)的平均值,最大值,最小值,以及利潤(rùn)總和。
LongSummaryStatistics longSummaryStatistics = departmentList
.stream()
.mapToLong(Department::getDepartProfit)
.summaryStatistics();
System.out.println("利潤(rùn)最大值"+longSummaryStatistics.getMax());
System.out.println("利潤(rùn)最小值"+longSummaryStatistics.getMin());
System.out.println("利潤(rùn)平均值"+longSummaryStatistics.getAverage());
System.out.println("利潤(rùn)總和"+longSummaryStatistics.getSum());這些統(tǒng)計(jì)值在所有特殊處理的 Stream,如 DoubleStream、LongStream 中都可以得出。如無(wú)需全部的統(tǒng)計(jì)值,也可分別調(diào)用 min、max、average 或 sum 方法獲得單個(gè)的統(tǒng)計(jì)值,同樣,三種基本類型對(duì)應(yīng)的特殊 Stream 也都包含這些方法。
6. 數(shù)據(jù)并行化
并 行 化 操 作 流 只 需 改 變 一 個(gè) 方 法 調(diào) 用。 如 果 已 經(jīng) 有 一 個(gè) Stream 對(duì) 象, 調(diào) 用 它 的parallel 方法就能讓其擁有并行操作的能力。如果想從一個(gè)集合類創(chuàng)建一個(gè)流,調(diào)用parallelStream 就能立即獲得一個(gè)擁有并行能力的流。
需求:并行的統(tǒng)計(jì)公司的所有的人員。
Set<Person> collect = departmentList.stream() .parallel() .flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet()); Set<Person> collect1 = departmentList.parallelStream() .flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet());
在底層,并行流還是沿用了 fork/join 框架。fork 遞歸式地分解問(wèn)題,然后每段并行執(zhí)行,最終由 join 合并結(jié)果,返回最后的值。
到此這篇關(guān)于Java8 函數(shù)式編程stream流使用詳解的文章就介紹到這了,更多相關(guān)Java8 函數(shù)式編程stream流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring+SpringMVC+MyBatis整合詳細(xì)教程(SSM)
Spring是一個(gè)開(kāi)源框架,Spring是于2003 年興起的一個(gè)輕量級(jí)的Java 開(kāi)發(fā)框架。這篇文章主要介紹了Spring+SpringMVC+MyBatis整合詳細(xì)教程(SSM),需要的朋友可以參考下2017-10-10
idea項(xiàng)目debug模式無(wú)法啟動(dòng)的解決
這篇文章主要介紹了idea項(xiàng)目debug模式無(wú)法啟動(dòng)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
通過(guò)Maven進(jìn)行jedis連接redis的實(shí)現(xiàn)
這篇文章主要介紹了通過(guò)Maven進(jìn)行jedis連接redis的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
Java設(shè)計(jì)模式之策略模式的使用(Strategy?Pattern)
策略模式是一種行為型設(shè)計(jì)模式,用于定義一系列算法并將每個(gè)算法封裝起來(lái),使它們可以互相替換,從而實(shí)現(xiàn)代碼的可維護(hù)性和靈活性,策略模式包含策略接口、具體策略類和上下文類,并通過(guò)將算法的選擇與使用分離,使得算法可以獨(dú)立變化2025-03-03

