今天的場景設(shè)計(jì)是這樣的:
給定一批學(xué)生分?jǐn)?shù)的數(shù)據(jù),求出所有男學(xué)生的平均分?jǐn)?shù)谁不。
如果這個命題放在sql中,應(yīng)該是送分題漾峡。在Java中去實(shí)現(xiàn)深夯,可能也沒有那么難。但是當(dāng)場景不斷復(fù)雜化乃正,我們就需要一些技巧來解決這類問題了住册。
假設(shè)Student類的數(shù)據(jù)結(jié)構(gòu)如下:
@Data
public class Student {
/**
* 學(xué)生ID
*/
private String id;
/**
* 學(xué)生姓名
*/
private String name;
/**
* 學(xué)生年齡
*/
private Integer age;
/**
* 學(xué)生性別 0-女 1-男
*/
private Integer gender;
/**
* 學(xué)生成績
*/
private Double score;
}
傳統(tǒng)思路
假設(shè)學(xué)生的數(shù)據(jù)是以List<Student>的形式給出的,讓我們先來回顧一下傳統(tǒng)思路是怎么解決這個問題的瓮具,由于
平均分?jǐn)?shù)=總分/人數(shù)
因此荧飞,我們需要一個臨時變量去記錄總分,另一個臨時變量去記錄男學(xué)生的人數(shù)名党,然后我們遍歷學(xué)生的列表叹阔,如果遍歷到的學(xué)生為男學(xué)生,則總分加上當(dāng)前學(xué)生的分?jǐn)?shù)传睹,人數(shù)加1耳幢,相關(guān)代碼如下:
Double totalScore = 0.0;
int count = 0;
for (Student student : students) {
// 男學(xué)生
if (student.getGender() == 1) {
totalScore += student.getScore();
count++;
}
}
Double average = totalScore / count;
System.out.println(average);
這樣的思路屬于命令式編程的范式,即我們一步一步告訴計(jì)算機(jī)先做什么再做什么欧啤,其好處是邏輯簡單睛藻,容易理解和編寫,也容易調(diào)試邢隧。但是這樣的方式編程通常代碼量巨大修档,并且很容易編寫出執(zhí)行效率低下的代碼,處理復(fù)雜邏輯時更是容易丟掉代碼的可讀性府框。
Stream流式計(jì)算
在Jdk8以后吱窝,Java引入了lambda表達(dá)式讥邻,使得Java可以更方便地使用函數(shù)式的風(fēng)格編寫程序。而同一版本中Stream的引入更是極大簡化了集合的操作院峡。
那么就讓我們來看一下在Stream的幫助下如何解決上面的問題:
Double average = students.stream()
.filter(s -> s.getGender() == 1)
.collect(Collectors.averagingDouble(Student::getScore));
System.out.println(average);
首先通過列表的stream()方法將列表轉(zhuǎn)為流兴使,再通過filter方法對流中的元素進(jìn)行過濾,最后通過collect方法對流中的元素進(jìn)行歸并照激,得到最終的結(jié)果发魄。事實(shí)上,所有使用流的場景都遵循這三個步驟俩垃,即流的創(chuàng)建励幼、流的轉(zhuǎn)換以及流的歸并。
上述流式計(jì)算的方式是一種函數(shù)式編程的風(fēng)格口柳,同時也是屬于聲明式編程的范式苹粟。相比于命令式編程,聲明式編程更強(qiáng)調(diào)告訴計(jì)算機(jī)要做什么跃闹,而不是具體怎么做嵌削。每個步驟具體的實(shí)現(xiàn)方案由計(jì)算機(jī)內(nèi)部自行實(shí)現(xiàn)。當(dāng)然望艺,這也依賴于Jdk內(nèi)部提供的強(qiáng)大的api苛秕。
更復(fù)雜的場景
讓我們把場景變得更復(fù)雜一些,來見識一樣流式計(jì)算的威力找默。
復(fù)雜場景1:學(xué)生分屬于不同班艇劫,計(jì)算每個班男同學(xué)的平均分
學(xué)生的類增加相應(yīng)字段,改造為:
@Builder
@Data
public class Student {
/**
* 學(xué)生ID
*/
private String id;
/**
* 學(xué)生姓名
*/
private String name;
/**
* 學(xué)生年齡
*/
private Integer age;
/**
* 學(xué)生性別 0-女 1-男
*/
private Integer gender;
/**
* 學(xué)生成績
*/
private Double score;
/**
* 學(xué)生屬于哪個班
*/
private Integer classNumber;
}
上述需求實(shí)現(xiàn)代碼如下:
final Map<Integer, Double> averageMap = students.stream()
.filter(s -> s.getGender() == 1)
.collect(Collectors.groupingBy(Student::getClassNumber,
Collectors.averagingDouble(Student::getScore)));
System.out.println(averageMap);
由于需要每個班的成績惩激,我們對學(xué)生按班級進(jìn)行分組港准,使用的是Collectors工具類提供的groupingBy()方法。這個方法第一個參數(shù)是分類的依據(jù)咧欣,這里傳的是Student::getClassNumber這個方法引用浅缸,即怎么根據(jù)學(xué)生對象獲取到學(xué)生的班級。第二個參數(shù)傳的是下游的收集器魄咕,即分組之后對每組元素做怎樣的操作衩椒,這里和之前一樣傳的是對學(xué)生的成績?nèi)∑骄值牟僮鳌H绻覀冎粚?shù)據(jù)進(jìn)行分組哮兰,不進(jìn)行后續(xù)處理毛萌,第二個參數(shù)可以不傳(重載方法)。
復(fù)雜場景2:計(jì)算分?jǐn)?shù)高于平均分的學(xué)生人數(shù)
// 先求平均分
final Double average = students.stream()
.collect(Collectors.averagingDouble(Student::getScore));
// 再求超過平均分的人數(shù)
final long count = students.stream()
.filter(s -> s.getScore() > average)
.count();
System.out.println(count);
這個需求想整合成一次流式操作比較困難喝滞,我們需要先獲取班級的平均分阁将,再去計(jì)算分?jǐn)?shù)超過平均分的人數(shù)。需要注意的是右遭,Stream對象是“一次性的”做盅,當(dāng)一次歸并操作完成后缤削,Stream就會被關(guān)閉,這時如果復(fù)用之前的對象就會拋出異常吹榴。
這里只舉這兩個例子亭敢,Stream還有很多方便的API,感興趣的可以自行嘗試图筹∷У叮總結(jié)一下,使用Stream可以極大簡化集合相關(guān)的操作远剩,如果有相關(guān)的數(shù)據(jù)處理需求扣溺,可以嘗試使用。