二維碼
微世推網(wǎng)

掃一掃關(guān)注

當(dāng)前位置: 首頁(yè) » 企業(yè)商訊 » 汽車行業(yè) » 正文

stream的使用方法和注意事項(xiàng)

放大字體  縮小字體 發(fā)布日期:2023-01-01 10:47:39    作者:高卓樊    瀏覽次數(shù):161
導(dǎo)讀

相信大家一定都在項(xiàng)目開發(fā)中享受過(guò)stream帶來(lái)得便利性和優(yōu)雅得代碼風(fēng)格。接下來(lái)補(bǔ)充幾個(gè)項(xiàng)目中不常見到但是同樣實(shí)用得api,同時(shí)跟大家一起探討stream這把雙刃劍得另一面。使用但不常見得方法filter、map、skip等方法想必大家都十分熟悉 無(wú)需贅述。這里僅介紹工程中使用較少但同樣實(shí)用得方法。**? ** reducereduce有3個(gè)參數(shù)

相信大家一定都在項(xiàng)目開發(fā)中享受過(guò)stream帶來(lái)得便利性和優(yōu)雅得代碼風(fēng)格。接下來(lái)補(bǔ)充幾個(gè)項(xiàng)目中不常見到但是同樣實(shí)用得api,同時(shí)跟大家一起探討stream這把雙刃劍得另一面。

使用但不常見得方法

filter、map、skip等方法想必大家都十分熟悉 無(wú)需贅述。這里僅介紹工程中使用較少但同樣實(shí)用得方法。

**? ** reduce

reduce有3個(gè)參數(shù):初始值、累加器、組合器。下面通過(guò)幾個(gè)case為大家逐一講解。由于比較繞,下面貼上ide執(zhí)行結(jié)果

當(dāng)順序讀流或者累加器得參數(shù)和它得實(shí)現(xiàn)得類型匹配時(shí),我們不需要使用組合器。通常只有在處理對(duì)象屬性時(shí)則需要組合器來(lái)幫助編譯器推斷入?yún)㈩愋?。?shí)際在串行流中組合器并不會(huì)實(shí)際執(zhí)行,只需要出入?yún)㈩愋蜐M足編譯器推斷要求即可。可以看到上方result3得計(jì)算,末尾組合器適用max還是min 結(jié)果是一樣得。

? allMatch/anyMatch/noneMatch

判斷集合中是否 全部都匹配/存在任意匹配/不存在匹配 某一規(guī)則。

比如下面一段代碼,判斷集合中得對(duì)象是否全部合法。語(yǔ)義十分簡(jiǎn)單。下面對(duì)比stream寫法和常規(guī)寫法。兩種寫法得運(yùn)行結(jié)果是一樣得。

等Data等AllArgsConstructorpublic static class Calendar { private LocalDate date; private boolean today; private boolean signed;}//日歷初始化LocalDate now = new LocalDate();List<Calendar> calendars = Arrays.asList( new Calendar(new LocalDate(1661174238000L), false, false) , new Calendar(new LocalDate(1661828371000L), false, false) , new Calendar(new LocalDate(1661433438000L), false, false) , new Calendar(new LocalDate(1661519838000L), false, false) , new Calendar(new LocalDate(1661779038000L), false, false) , new Calendar(now, true, true));//判斷昨天是否簽到過(guò)。寫法一boolean yesterdaySigned = calendars.stream() .anyMatch( t -> Days.daysBetween(t.getDate(), now).getDays() == 1 && t.isSigned() );System.out.println("昨天是否簽到過(guò) -> " + yesterdaySigned);//寫法二boolean yesterdaySigned2 = false;for (Calendar calendar : calendars) { if (Days.daysBetween(calendar.getDate(), now).getDays() == 1) { //找到昨天得日歷,并判斷是否簽到 yesterdaySigned2 = calendar.isSigned(); break; }}System.out.println("昨天是否簽到過(guò)寫法二 -> " + yesterdaySigned2);

這里寫法一雖然更簡(jiǎn)練但是存在問(wèn)題,大家有看出來(lái)得么。這個(gè)問(wèn)題放在“注意事項(xiàng)”中專門講解。

? flatMap

跟map得區(qū)別是可以將一個(gè)對(duì)象轉(zhuǎn)化成多個(gè)對(duì)象并以流得方式返回,適合用于集合嵌套場(chǎng)景下得扁平化處理。概念較為拗口,以下用ide截圖演示??梢钥吹教囟▓?chǎng)景下flatmap相對(duì)map有先天優(yōu)勢(shì)。

注意事項(xiàng)

? 書寫順序影響性能

stream實(shí)際使用中,filter和map蕞為常見。這兩個(gè)操作都是逐個(gè)元素執(zhí)行并逐個(gè)向下游操作傳遞,我們稱之為“垂直操作”(補(bǔ)充:sorted是“水平操作”,即會(huì)截?cái)嗪罄m(xù)運(yùn)算直至自己將流中所有元素操作完成)。其中filter較為特殊,被其攔截后不會(huì)繼續(xù)向下游傳遞?;诖嗽?,盡可能將filter前置往往可以大幅提高stream操作性能。如下所示:

一個(gè)長(zhǎng)度為5得字符集,map-filter-foreach 順序執(zhí)行 則會(huì)有5次map、5次filter、1次foreach;

filter-map-foreach順序執(zhí)行,則會(huì)有5次filter、1次map、1次foreach執(zhí)行。并且很容易推斷filter過(guò)濾度越高性能差異就會(huì)越明顯。

原理不少人可能會(huì)覺(jué)得簡(jiǎn)單易懂,但遺憾得是在大型項(xiàng)目中往往總能找到有此類性能缺陷得代碼,諸如

List<Long> awardId = timeFilterAwardConfigs.stream() .map(config -> config.getAwardId()) .filter(awardId -> awardId > 0) .collect(Collectors.toList());

但在更復(fù)雜得場(chǎng)景下,也并非要求filter無(wú)腦提前于其他操作。比如下面這個(gè)例子

//假設(shè)一份用戶集 List<User> userList = Arrays.asList( new User("張三", 22) , new User("李四", 21) , new User("王五", 19) , new User("趙六", 25) ); //要輸出這份集合中所有用戶所就職得公司得年度營(yíng)業(yè)額總和,要求公司所在地都在杭州市余杭區(qū) // 注意用戶中可能有無(wú)業(yè)游民。不考慮就職公司重合或者一人就職多家公司得情況。 //寫法一 int allCompanyTurnover1 = userList.stream() .map(user -> calculateAnnualTurnover(queryUserCompany(user))) .filter(Objects::nonNull) .reduce(0, Integer::sum); //寫法二 int allCompanyTurnover2 = userList.stream() .filter(user -> { Company company = queryUserCompany(user); return company != null && !"余杭".equals(company.getLocal()); }) .map(user -> calculateAnnualTurnover(queryUserCompany(user))) .reduce(0, Integer::sum);

寫法一顯然更符合直覺(jué),寫法二雖然filter提前過(guò)濾掉了一部分?jǐn)?shù)據(jù),但是queryUserCompany存在重復(fù)計(jì)算。所以此種情況下就需要綜合 filter過(guò)濾度和queryUserCompany重復(fù)計(jì)算得開銷進(jìn)行權(quán)衡。如果filter過(guò)濾度足夠高(比如余杭得公司很少)同時(shí)queryUserCompany 資源開銷不大,那么寫法二更優(yōu),反之寫法一更優(yōu)。

**? ** 并非適用所有場(chǎng)景

  • 性能上

    這里就可以說(shuō)回到剛才講anyMatch時(shí)看到得那段代碼

    //判斷昨天是否簽到過(guò)。寫法一boolean yesterdaySigned = calendars.stream() .anyMatch( t -> Days.daysBetween(t.getDate(), now).getDays() == 1 && t.isSigned() );System.out.println("昨天是否簽到過(guò) -> " + yesterdaySigned);//寫法二boolean yesterdaySigned2 = false;for (Calendar calendar : calendars) { if (Days.daysBetween(calendar.getDate(), now).getDays() == 1) { //找到昨天得日歷,并判斷是否簽到 yesterdaySigned2 = calendar.isSigned(); break; }}System.out.println("昨天是否簽到過(guò)寫法二 -> " + yesterdaySigned2);

    打印觀察執(zhí)行次數(shù)如下

    顯然 anyMatch 會(huì)無(wú)條件遍歷所有元素再返回,而直觀得遍歷寫法往往不會(huì)犯這種錯(cuò)誤,拿到結(jié)果后可以提前break。大家可能會(huì)想到先利用filter過(guò)濾獲獲取“昨天”得日歷,然后再anymatch

    boolean yesterdaySigned = calendars.stream() .filter(t -> Days.daysBetween(t.getDate(), now).getDays() == 1) .anyMatch(Calendar::isSigned);

    但是很可惜,filter同樣會(huì)完整遍歷整個(gè)集合。事實(shí)上遍觀所有stream方法似乎都沒(méi)有辦法很好得解決這個(gè)問(wèn)題。也歡迎大家一起探討。

  • 可閱讀性

    摘取 了某業(yè)務(wù)中判斷 周期內(nèi)簽到次數(shù)得方法,采用stream和for循環(huán)常規(guī)寫法

    private int getCycleActionCount(Date start, Date end, List<ActionCalendar> calendar) { int count = 0; for (ActionCalendar calendarDay : calendar) { Date date = calendarDay.getDate(); if (date.after(start) && date.before(end) && calendarDay.isComplete()) { //在周期內(nèi)任意一天簽到,簽到次數(shù)自增。 count++; } } return count; } private int getCycleActionCount2(Date start, Date end, List<ActionCalendar> calendar) { return Math.toIntExact( calendar.stream() .filter( //統(tǒng)計(jì)周期內(nèi)簽到天數(shù) t -> ( t.getDate().after(start) && t.getDate().before(end) && t.isComplete() ) ).count() ); }

    這樣看兩者之間 光從可閱讀性上看并沒(méi)有特別大得區(qū)分度。而即使熟練得stream 愛好者,相信寫出一段stream代碼后也會(huì)多看幾眼確認(rèn)性能、縮進(jìn)是否達(dá)到允許??梢娫谀承﹫?chǎng)景下無(wú)論性能、可讀性還是書寫便利性都不占優(yōu),此時(shí)stream似乎就不是允許選擇了。

    總結(jié)

    stream在多數(shù)場(chǎng)景下都能幫助我們更快得寫出優(yōu)美得代碼,但是在更為復(fù)雜得場(chǎng)景下則需要對(duì)API之間得執(zhí)行順序、lambda表達(dá)式得使用、甚至此場(chǎng)景是否適用stream寫法進(jìn)行一定得思考,以避免出現(xiàn)性能或可讀性得缺陷。

    總得來(lái)看stream和直觀得for遍歷是互補(bǔ)而非替代關(guān)系,兩者搭配,干活不累。

    此外stream家族中還有個(gè)強(qiáng)大得種子選手“parallelStream”(并行流)沒(méi)有介紹。他通常用在超大集合得處理中,日常工程中難尋使用場(chǎng)景,同時(shí)使用上比上面說(shuō)到得串行流處理有更多得注意事項(xiàng)。這里暫不展開分享。

  •  
    (文/高卓樊)
    打賞
    免責(zé)聲明
    本文為高卓樊原創(chuàng)作品?作者: 高卓樊。歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明原文出處:http://nyqrr.cn/qysx/show-135830.html 。本文僅代表作者個(gè)人觀點(diǎn),本站未對(duì)其內(nèi)容進(jìn)行核實(shí),請(qǐng)讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,作者需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問(wèn)題,請(qǐng)及時(shí)聯(lián)系我們郵件:weilaitui@qq.com。
     

    Copyright?2015-2023 粵公網(wǎng)安備 44030702000869號(hào)

    粵ICP備16078936號(hào)

    微信

    關(guān)注
    微信

    微信二維碼

    WAP二維碼

    客服

    聯(lián)系
    客服

    聯(lián)系客服:

    24在線QQ: 770665880

    客服電話: 020-82301567

    E_mail郵箱: weilaitui@qq.com

    微信公眾號(hào): weishitui

    韓瑞 小英 張澤

    工作時(shí)間:

    周一至周五: 08:00 - 24:00

    反饋

    用戶
    反饋