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 的類型??梢园汛a改為如下可以通過(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實(shí)現(xiàn)計(jì)數(shù)排序,桶排序和基數(shù)排序
這篇文章主要為大家詳細(xì)介紹了計(jì)數(shù)排序,桶排序和基數(shù)排序的多種語(yǔ)言的實(shí)現(xiàn)(JavaScript、Python、Go語(yǔ)言、Java),感興趣的小伙伴可以了解一下2022-12-12一文搞懂MyBatis多數(shù)據(jù)源Starter實(shí)現(xiàn)
本文將實(shí)現(xiàn)一個(gè)MyBatis的Springboot的Starter包,引用這個(gè)Starter包后,僅需要提供少量配置信息,就能夠完成MyBatis多數(shù)據(jù)源的初始化和使用,需要的小伙伴可以參考一下2023-04-04springboot+mybatis plus實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)查詢
實(shí)際開(kāi)發(fā)過(guò)程中經(jīng)常需要查詢節(jié)點(diǎn)樹(shù),根據(jù)指定節(jié)點(diǎn)獲取子節(jié)點(diǎn)列表,本文主要介紹了springboot+mybatis plus實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)查詢,感興趣的可以了解一下2021-07-07java為什么使用BlockingQueue解決競(jìng)態(tài)條件問(wèn)題面試精講
這篇文章主要為大家介紹了java為什么使用BlockingQueue解決競(jìng)態(tài)條件問(wèn)題面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Mybatis-plus配置分頁(yè)插件返回統(tǒng)一結(jié)果集
本文主要介紹了Mybatis-plus配置分頁(yè)插件返回統(tǒng)一結(jié)果集,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06淺談Java之Map 按值排序 (Map sort by value)
下面小編就為大家?guī)?lái)一篇淺談Java之Map 按值排序 (Map sort by value)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08關(guān)于easyExcel中讀取Excel表頭的實(shí)例說(shuō)明
EasyExcel是阿里巴巴開(kāi)源的一個(gè)excel處理框架,以使用簡(jiǎn)單、節(jié)省內(nèi)存著稱,下面這篇文章主要給大家介紹了關(guān)于easyExcel中讀取Excel表頭的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06SpringCloud微服務(wù)架構(gòu)升級(jí)匯總
這篇文章主要介紹了SpringCloud微服務(wù)架構(gòu)升級(jí)匯總,它提倡將單一應(yīng)用程序劃分成一組小的服務(wù),服務(wù)之間互相協(xié)調(diào)、互相配合,為用戶提供最終價(jià)值,需要的朋友可以參考下2019-06-06