Java?Stream.reduce()用法詳細(xì)解析
在學(xué)習(xí)這個(gè)函數(shù)的用法之前,我們要先知道這個(gè)函數(shù)參數(shù)的意義
基本使用
先舉一個(gè)簡(jiǎn)單的例子:
算法題:Words
題目描述
每個(gè)句子由多個(gè)單詞組成,句子中的每個(gè)單詞的長(zhǎng)度都可能不一樣,我們假設(shè)每個(gè)單詞的長(zhǎng)度Ni為該單詞的重量,你需要做的就是給出整個(gè)句子的平均重量V。解答要求
時(shí)間限制:1000ms, 內(nèi)存限制:100MB
輸入
輸入只有一行,包含一個(gè)字符串S(長(zhǎng)度不會(huì)超過(guò)100),代表整個(gè)句子,句子中只包含大小寫的英文字母,每個(gè)單詞之間有一個(gè)空格。輸出
輸出句子S的平均重量V(四舍五入保留兩位小數(shù))。Who Love Solo
輸出樣例
3.67
這道題的意思是求一句話中每個(gè)單詞的平均長(zhǎng)度,我們求得總長(zhǎng)度然后除以單詞數(shù)量即可,剛好能用到reduce()這個(gè)方法。
public class Demo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] s = sc.nextLine().split(" ");
double res = Arrays.stream(s).mapToDouble(a ->a.length()).reduce(0,(a,b)->a+b);
System.out.println(String.format("%.2f",res/s.length));
}
}
在代碼中,.reduce(0,(a,b)->a+b);這一塊就是我們經(jīng)典的使用案例,我們要先明白其中a,b的含義,然后再學(xué)習(xí)如何使用
關(guān)鍵概念:初始值的定義(Identity),累加器(Accumulator),組合器(Combiner)
- Identity : 定義一個(gè)元素代表是歸并操作的初始值,如果Stream 是空的,也是Stream 的默認(rèn)結(jié)果
- Accumulator: 定義一個(gè)帶兩個(gè)參數(shù)的函數(shù),第一個(gè)參數(shù)是上個(gè)歸并函數(shù)的返回值,第二個(gè)是Strem 中下一個(gè)元素。
- Combiner: 調(diào)用一個(gè)函數(shù)來(lái)組合歸并操作的結(jié)果,當(dāng)歸并是并行執(zhí)行或者當(dāng)累加器的函數(shù)和累加器的實(shí)現(xiàn)類型不匹配時(shí)才會(huì)調(diào)用此函數(shù)。
也就是說(shuō)0就是我們的初始值,(a,b)->a+b就是我們的累加器,其中a就是上一次的計(jì)算結(jié)果,b就是Stream流中當(dāng)前元素,而后面的a+b則是計(jì)算規(guī)則,比如如果我們改成a*b,那就是計(jì)算乘積了,當(dāng)然我們也可以用方法引用來(lái)代替 lambda 表達(dá)式。
double res = Arrays.stream(s).mapToDouble(a ->a.length()).reduce(0,Double::sum);
這就是最基本的使用了,不知道小伙伴們有沒(méi)有學(xué)會(huì)呢?
額外舉例
當(dāng)然,我們可以用reduce 方法處理其他類型的 stream,例如,可以操作一個(gè) String 類型的數(shù)組,把數(shù)組的字符串進(jìn)行拼接。
List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
String result = letters
.stream()
.reduce("", (partialString, element) -> partialString + element);
assertThat(result).isEqualTo("abcde");
同樣也可以用方法引用來(lái)簡(jiǎn)化代碼
String result = letters.stream().reduce("", String::concat);
assertThat(result).isEqualTo("abcde");我們?cè)侔焉厦娴钠唇幼址睦痈南滦枨?,先把字符串轉(zhuǎn)變成大寫然后再拼接
String result = letters
.stream()
.reduce(
"", (partialString, element) -> partialString.toUpperCase() + element.toUpperCase());
assertThat(result).isEqualTo("ABCDE");另外,我們可以并行地歸并元素(并行歸并,下面會(huì)詳細(xì)講解),如下并行歸并一個(gè)數(shù)字?jǐn)?shù)組來(lái)求和
List<Integer> ages = Arrays.asList(25, 30, 45, 28, 32); int computedAges = ages.parallelStream().reduce(0, a, b -> a + b, Integer::sum);
當(dāng)對(duì)一個(gè)流進(jìn)行并行操作時(shí),在運(yùn)行時(shí)會(huì)把流分割多個(gè)子流來(lái)并行操作。在上面例子中,我們需要一個(gè)函數(shù)來(lái)組合各個(gè)子流返回的結(jié)果,這個(gè)函數(shù)就是前面提到的Combiner(組合器)。
有一個(gè)注意點(diǎn),下面的代碼無(wú)法通過(guò)編譯
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges =
users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());上代碼無(wú)法編譯的原因是,流中包含的是User 對(duì)象,但是累加函數(shù)的參數(shù)分別是數(shù)字和user 對(duì)象,而累加器的實(shí)現(xiàn)是求和,所以編譯器無(wú)法推斷參數(shù) user 的類型。可以把代碼改為如下可以通過(guò)編譯
int result = users.stream() .reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum); assertThat(result).isEqualTo(65);
當(dāng)順序讀流或者累加器的參數(shù)和它的實(shí)現(xiàn)的類型匹配時(shí),我們不需要使用組合器。
并行讀流
如上文提到的,我們可以并行的使用 reduce() 方法。并行使用時(shí),要注意一下幾點(diǎn):
- 結(jié)果和處理的順序無(wú)關(guān)
- 操作不影響原有數(shù)據(jù)
- 操作沒(méi)有狀態(tài)和同樣的輸入有一樣的輸出結(jié)果
我們注意上面3點(diǎn),以防出現(xiàn)不預(yù)期的結(jié)果,一般并行處理包含大量數(shù)據(jù)的流或者耗時(shí)的操作。
處理異常
在以上的例子中,reduce 方法都沒(méi)拋出異常,如果出現(xiàn)異常我們?cè)撊绾蝺?yōu)雅的處理異常呢?看下面例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); int divider = 2; int result = numbers.stream().reduce(0, a / divider + b / divider);
如果 divider =0 , 會(huì)拋出 ArithmeticException,遇到這種情況,一般的處理方法使用 try/catch 捕獲異常
public static int divideListElements(List<Integer> values, int divider) {
return values.stream()
.reduce(0, (a, b) -> {
try {
return a / divider + b / divider;
} catch (ArithmeticException e) {
LOGGER.log(Level.INFO, "Arithmetic Exception: Division by Zero");
}
return 0;
});
}如果直接使用 try/catch 會(huì)影響代碼的可讀性,我們可以把 divide 的操作封裝一個(gè)單獨(dú)的方法,并在里面捕獲異常,如下:
rivate static int divide(int value, int factor) {
int result = 0;
try {
result = value / factor;
} catch (ArithmeticException e) {
LOGGER.log(Level.INFO, "Arithmetic Exception: Division by Zero");
}
return result
}divideListElements 調(diào)用 divide 方法
public static int divideListElements(List<Integer> values, int divider) {
return values.stream().reduce(0, (a, b) -> divide(a, divider) + divide(b, divider));
}復(fù)雜對(duì)象的處理
我們可以使用 reduce 方法處理復(fù)雜的對(duì)象,reduce 需要接受和復(fù)雜對(duì)象相對(duì)應(yīng)的 identity、accumulator、combiner。
假設(shè)一個(gè)場(chǎng)景:計(jì)算一個(gè)網(wǎng)站用戶的評(píng)分,該評(píng)分是所有用戶所有評(píng)論的平均值。
有個(gè)類 Review 定義如下:
public class Review {
private int points;
private String review;
// constructor, getters and setters
}類 Rating 引用 Review 計(jì)算用戶的評(píng)分
public class Rating {
double points;
List<Review> reviews = new ArrayList<>();
public void add(Review review) {
reviews.add(review);
computeRating();
}
private double computeRating() {
double totalPoints =
reviews.stream().map(Review::getPoints).reduce(0, Integer::sum);
this.points = totalPoints / reviews.size();
return this.points;
}
public static Rating average(Rating r1, Rating r2) {
Rating combined = new Rating();
combined.reviews = new ArrayList<>(r1.reviews);
combined.reviews.addAll(r2.reviews);
combined.computeRating();
return combined;
}
}先組裝一些用戶和用戶的評(píng)論
User john = new User("John", 30);
john.getRating().add(new Review(5, ""));
john.getRating().add(new Review(3, "not bad"));
User julie = new User("Julie", 35);
john.getRating().add(new Review(4, "great!"));
john.getRating().add(new Review(2, "terrible experience"));
john.getRating().add(new Review(4, ""));
List<User> users = Arrays.asList(john, julie);調(diào)用 reduce 方法處理評(píng)分
Rating averageRating = users.stream()
.reduce(new Rating(),
(rating, user) -> Rating.average(rating, user.getRating()),
Rating::average);不知道大家學(xué)會(huì)了嗎?
總結(jié)
到此這篇關(guān)于Java Stream.reduce()用法的文章就介紹到這了,更多相關(guān)Stream.reduce()用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解
這篇文章主要介紹了java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解的相關(guān)資料,希望通過(guò)本文大家能夠理解掌握動(dòng)態(tài)代理機(jī)制,需要的朋友可以參考下2017-09-09
mybatis修改int型數(shù)據(jù)無(wú)法修改成0的解決
這篇文章主要介紹了mybatis修改int型數(shù)據(jù)無(wú)法修改成0的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot整合JavaMail通過(guò)阿里云企業(yè)郵箱發(fā)送郵件的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot整合JavaMail通過(guò)阿里云企業(yè)郵箱發(fā)送郵件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
mybatis中@Param注解總是報(bào)取不到參數(shù)問(wèn)題及解決
這篇文章主要介紹了mybatis中@Param注解總是報(bào)取不到參數(shù)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析
這篇文章主要為大家介紹了Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Java 集合中關(guān)于Iterator和ListIterator的用法說(shuō)明
這篇文章主要介紹了Java 集合中關(guān)于Iterator和ListIterator的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Java SpringBoot的相關(guān)知識(shí)點(diǎn)詳解
這篇文章主要介紹了SpringBoot的相關(guān)知識(shí)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-10-10

