Java?Stream.reduce()用法詳細(xì)解析
在學(xué)習(xí)這個函數(shù)的用法之前,我們要先知道這個函數(shù)參數(shù)的意義
基本使用
先舉一個簡單的例子:
算法題:Words
題目描述
每個句子由多個單詞組成,句子中的每個單詞的長度都可能不一樣,我們假設(shè)每個單詞的長度Ni為該單詞的重量,你需要做的就是給出整個句子的平均重量V。解答要求
時間限制:1000ms, 內(nèi)存限制:100MB
輸入
輸入只有一行,包含一個字符串S(長度不會超過100),代表整個句子,句子中只包含大小寫的英文字母,每個單詞之間有一個空格。輸出
輸出句子S的平均重量V(四舍五入保留兩位小數(shù))。Who Love Solo
輸出樣例
3.67
這道題的意思是求一句話中每個單詞的平均長度,我們求得總長度然后除以單詞數(shù)量即可,剛好能用到reduce()這個方法。
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 : 定義一個元素代表是歸并操作的初始值,如果Stream 是空的,也是Stream 的默認(rèn)結(jié)果
- Accumulator: 定義一個帶兩個參數(shù)的函數(shù),第一個參數(shù)是上個歸并函數(shù)的返回值,第二個是Strem 中下一個元素。
- Combiner: 調(diào)用一個函數(shù)來組合歸并操作的結(jié)果,當(dāng)歸并是并行執(zhí)行或者當(dāng)累加器的函數(shù)和累加器的實(shí)現(xiàn)類型不匹配時才會調(diào)用此函數(shù)。
也就是說0
就是我們的初始值,(a,b)->a+b
就是我們的累加器,其中a
就是上一次的計算結(jié)果,b
就是Stream流中當(dāng)前元素,而后面的a+b
則是計算規(guī)則,比如如果我們改成a*b
,那就是計算乘積了,當(dāng)然我們也可以用方法引用來代替 lambda 表達(dá)式。
double res = Arrays.stream(s).mapToDouble(a ->a.length()).reduce(0,Double::sum);
這就是最基本的使用了,不知道小伙伴們有沒有學(xué)會呢?
額外舉例
當(dāng)然,我們可以用reduce 方法處理其他類型的 stream,例如,可以操作一個 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");
同樣也可以用方法引用來簡化代碼
String result = letters.stream().reduce("", String::concat); assertThat(result).isEqualTo("abcde");
我們再把上面的拼接字符串的例子改下需求,先把字符串轉(zhuǎn)變成大寫然后再拼接
String result = letters .stream() .reduce( "", (partialString, element) -> partialString.toUpperCase() + element.toUpperCase()); assertThat(result).isEqualTo("ABCDE");
另外,我們可以并行地歸并元素(并行歸并,下面會詳細(xì)講解),如下并行歸并一個數(shù)字?jǐn)?shù)組來求和
List<Integer> ages = Arrays.asList(25, 30, 45, 28, 32); int computedAges = ages.parallelStream().reduce(0, a, b -> a + b, Integer::sum);
當(dāng)對一個流進(jìn)行并行操作時,在運(yùn)行時會把流分割多個子流來并行操作。在上面例子中,我們需要一個函數(shù)來組合各個子流返回的結(jié)果,這個函數(shù)就是前面提到的Combiner
(組合器)。
有一個注意點(diǎn),下面的代碼無法通過編譯
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35)); int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
上代碼無法編譯的原因是,流中包含的是User 對象,但是累加函數(shù)的參數(shù)分別是數(shù)字和user 對象,而累加器的實(shí)現(xiàn)是求和,所以編譯器無法推斷參數(shù) user 的類型。可以把代碼改為如下可以通過編譯
int result = users.stream() .reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum); assertThat(result).isEqualTo(65);
當(dāng)順序讀流或者累加器的參數(shù)和它的實(shí)現(xiàn)的類型匹配時,我們不需要使用組合器。
并行讀流
如上文提到的,我們可以并行的使用 reduce() 方法。并行使用時,要注意一下幾點(diǎn):
- 結(jié)果和處理的順序無關(guān)
- 操作不影響原有數(shù)據(jù)
- 操作沒有狀態(tài)和同樣的輸入有一樣的輸出結(jié)果
我們注意上面3點(diǎn),以防出現(xiàn)不預(yù)期的結(jié)果,一般并行處理包含大量數(shù)據(jù)的流或者耗時的操作。
處理異常
在以上的例子中,reduce 方法都沒拋出異常,如果出現(xiàn)異常我們該如何優(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 , 會拋出 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 會影響代碼的可讀性,我們可以把 divide 的操作封裝一個單獨(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ù)雜對象的處理
我們可以使用 reduce 方法處理復(fù)雜的對象,reduce 需要接受和復(fù)雜對象相對應(yīng)的 identity、accumulator、combiner。
假設(shè)一個場景:計算一個網(wǎng)站用戶的評分,該評分是所有用戶所有評論的平均值。
有個類 Review 定義如下:
public class Review { private int points; private String review; // constructor, getters and setters }
類 Rating 引用 Review 計算用戶的評分
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; } }
先組裝一些用戶和用戶的評論
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 方法處理評分
Rating averageRating = users.stream() .reduce(new Rating(), (rating, user) -> Rating.average(rating, user.getRating()), Rating::average);
不知道大家學(xué)會了嗎?
總結(jié)
到此這篇關(guān)于Java Stream.reduce()用法的文章就介紹到這了,更多相關(guān)Stream.reduce()用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Java實(shí)現(xiàn)計數(shù)排序,桶排序和基數(shù)排序
這篇文章主要為大家詳細(xì)介紹了計數(shù)排序,桶排序和基數(shù)排序的多種語言的實(shí)現(xiàn)(JavaScript、Python、Go語言、Java),感興趣的小伙伴可以了解一下2022-12-12一文搞懂MyBatis多數(shù)據(jù)源Starter實(shí)現(xiàn)
本文將實(shí)現(xiàn)一個MyBatis的Springboot的Starter包,引用這個Starter包后,僅需要提供少量配置信息,就能夠完成MyBatis多數(shù)據(jù)源的初始化和使用,需要的小伙伴可以參考一下2023-04-04springboot+mybatis plus實(shí)現(xiàn)樹形結(jié)構(gòu)查詢
實(shí)際開發(fā)過程中經(jīng)常需要查詢節(jié)點(diǎn)樹,根據(jù)指定節(jié)點(diǎn)獲取子節(jié)點(diǎn)列表,本文主要介紹了springboot+mybatis plus實(shí)現(xiàn)樹形結(jié)構(gòu)查詢,感興趣的可以了解一下2021-07-07java為什么使用BlockingQueue解決競態(tài)條件問題面試精講
這篇文章主要為大家介紹了java為什么使用BlockingQueue解決競態(tài)條件問題面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Mybatis-plus配置分頁插件返回統(tǒng)一結(jié)果集
本文主要介紹了Mybatis-plus配置分頁插件返回統(tǒng)一結(jié)果集,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06淺談Java之Map 按值排序 (Map sort by value)
下面小編就為大家?guī)硪黄獪\談Java之Map 按值排序 (Map sort by value)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08關(guān)于easyExcel中讀取Excel表頭的實(shí)例說明
EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單、節(jié)省內(nèi)存著稱,下面這篇文章主要給大家介紹了關(guān)于easyExcel中讀取Excel表頭的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06