欧洲精品久久久av无码电影,日日噜噜夜夜爽爽,精品无码国产自产拍在线观看蜜,人妻少妇被粗大爽9797pw,羞国产在线拍揄自揄视频,国产白嫩漂亮美女在线观看,天码欧美日本一道免费,av大片在线无码免费

      歡迎來到同城快修-附近家電維修、家電清洗、家電安裝服務平臺

      24小時家電維修熱線:

      400—1558638

      當前位置:主頁 > 中央空調 > 維修資訊 >

      再聊Java Stream的一些實戰技能與注意點

      發布日期:2023-09-14 21:08:08 瀏覽:
      再聊Java Stream的一些實戰技能與注意點

      大家好,又見面了。

      在此前我的文章中,曾分2篇詳細探討了下JAVA中Stream流的相關操作,2篇文章收獲了累計 10w+閱讀、2k+點贊以及 5k+收藏的記錄。能夠得到眾多小伙伴的認可,是技術分享過程中最開心的事情。

      • 全面吃透JAVA Stream流操作,讓代碼更加的優雅
      • 講透JAVA Stream的collect用法與原理,遠比你想象的更強大

      不少小伙伴在評論中提出了一些的疑問或自己的獨到見解,也在評論區中進行了熱烈的互動討論。梳理了下相關評論內容,針對一些典型的討論點進行拿出來聊一聊,同時也是對此前兩篇Java Stream相關文章內容的補充完善。

      Stream處理時列表到底循環了多少次

      看下面這段Stream使用的常見場景:

      Stream.of(17, 22, 35, 12, 37)        .filter(age > age > 18)        .filter(age > age < 35)        .map(age > age + &34;歲&34;)        .collect(Collectors.toList());

      在這段代碼里面,同時有2個 filter操作和1個 map操作以及1個 collect操作,那么這段代碼執行的時候,究竟是對這個list執行了幾次循環操作呢?是每一個Stream步驟都會進行一次遍歷操作嗎?為了驗證這個問題,我們將上述代碼改寫一下,打印下每個步驟的結果:

              List<String> ages = Stream.of(17,22,35,12,37)                .filter(age > {                    System.out.println(&34;filter1 處理:&34; + age);                    return age > 18;                })                .filter(age > {                    System.out.println(&34;filter2 處理:&34; + age);                    return age < 35;                })                .map(age > {                    System.out.println(&34;map 處理:&34; + age);                    return age + &34;歲&34;;                })                .collect(Collectors.toList());

      先執行,得到如下的執行結果。其實結果已經很明顯的可以看出,stream流處理的時候,是對列表進行了一次循環,然后順序的執行給定的stream執行語句。

      按照上述輸出的結果,可以看出其處理的過程可以等價于如下的常規寫法:

              List<Integer> ages = Arrays.asList(17,22,35,12,37);        List<String> results = new ArrayList<>();        for (Integer age : ages) {            if (age > 18) {                if (age < 35) {                    results.add(age + &34;歲&34;);                }            }        }        System.out.println(results);

      所以,Stream并不會去遍歷很多次。其實上述邏輯也符合Stream 流水線加工的整體模式,試想一下,一條流水線上分環節加工一件商品,同一件產品也不會在流水線上加工2次的吧~

      Stream究竟是讓代碼更易讀還是更難懂

      自Java8引入了 Lambda、函數式接口、Stream等新鮮內容以來,針對使用Stream或Lambda語法究竟是讓代碼更易懂還是更復雜的爭議,一直就沒有停止過。有的同學會覺得Stream語法的方式,一眼就可以看出業務邏輯本身的含義,也有一些同學認為使用了Stream之后代碼的可讀性降低了很多。

      其實,這是個人編碼模式與理念上的不同感知而已。Stream主打的就是讓代碼更聚焦自身邏輯,省去其余繁文縟節對代碼邏輯的干擾,整體編碼上會更加的簡潔。但是剛接觸的時候,難免會需要一定的適應期。技術總是在不斷迭代、不斷擁抱新技術、不去刻意排斥新技術,或許是一個更好的選項。

      那么,話說回來,如何讓自己能夠一眼看懂Stream代碼、感受到Stream的簡潔之美呢?分享個人的一個經驗:

      1. 先了解幾個常見的Stream的api的功能含義(Stream的API封裝的很優秀,很多都是字面意義就可以理解)
      2. 改變意識,聚焦純粹的業務邏輯本身,不要在乎具體寫法細節

      下面舉了個例子,如何用上述的2條方法,快速的讓自己理解一段Stream代碼表達的意思。

      那么上面這段代碼的含義就是,先根據員工子公司過濾所有上海公司的人員,再獲取員工工資最高的那個人信息。怎么樣?按照這個方法,是不是可以發現,Stream的方式,確實更加容易理解了呢~

      在IDEA中debug調試Stream代碼段

      技術分享其實是一個雙向的過程,分享的同時,也是自我學習與提升的機會,除了可以梳理發現一些自己之前忽略的知識點并加以鞏固,還可以在互動的時候get到新的技能。

      比如,我在此前的 Java Stream介紹的文章中,有提過基于Stream進行編碼的時候會導致代碼 debug調試的時候會比較困難,尤其是那種只有一行Lambda表達式的情況(因為如果代碼邏輯多行編寫的時候,可以在代碼塊內部打斷點,這樣其實也可以進行debug調試)。

      關于這一點,很多小伙伴也有相同的感受,比如下面這個評論:

      你以為這就結束了?接下來一個小伙伴的提示,“震驚”了眾人!納尼?原來Stream代碼段也是可以debug單步調試的?

      跟蹤Stream中單步處理過程的操作入口按鈕長這樣:

      并且,另一個小伙伴補充說這是IDEA從 2019.03版本開始有的功能:

      嗯?難怪呢,我一直用的2019.02版本的,所以才沒用上這個功能(強行給自己找了個臺階、哈哈哈)。于是,我悄悄的將自己的idea升級到了最新的2023.02版本(PS:新版本的UI挺好看,就是bug賊多)。好啦,言歸正傳,那么究竟應該如何利用IDEA來實現單步DEBUG呢?一一起來感受下吧。

      在代碼行前面添加斷點的時候,如果要打斷點的這行代碼里面包含Stream中間方法(map\filter\sort之類的)的時候,會提示讓選擇斷點的具體類型。

      一共有三種類型斷點可供選擇:

      • Line:斷點打在這一行上,不會進入到具體的Stream執行函數塊中
      • Lambda:代碼打在內部的lambda代碼塊上
      • Line and Lambda:代碼走到這行或者執行這一行具體的函數塊內容的時候,都會進入斷點

      下面這個圖可以更清晰的解釋清楚上述三者的區別。一般來說,我們debug的時候,更多的是關注自身的業務具體邏輯,而不會過多去關注Stream執行框架的運轉邏輯,所以大部分情況下,我們選擇第二個Lambda選項即可

      按照上面所述,我們在代碼行前面添加一個Lambda類型斷點,然后debug模式啟動程序執行,等到斷點進入的時候便可以正常的進行debug并查看內部的處理邏輯了。

      如果遇到圖中這種只有一行的lambda形式代碼,想要看下返回值到底是什么的,可以選中執行的片段,然后 ALT+F8打開Evaluate界面(或者右鍵選擇 Evaluate Expression),點擊 Evaludate按鈕執行查看具體結果。

      大部分情況下,掌握這一點,已經可以應付日常的開發過程中對Stream代碼邏輯的debug訴求了。但是上述過程偏向于細節,如果需要看下整個Stream代碼段整體層面的執行與數據變化過程,就需要上面提到的Stream Trace功能。要想使用該功能,斷點的位置也是有講究的,必須要將斷點打在stream開流的地方,否則看不到任何內容。另外,對于一些新版本的IDEA而言,這個入口也比較隱蔽,藏在了下拉菜單中,就像下面這個樣子。

      我們找到Trace Current Stream Chain并點擊,可以打開Stream Trace界面,這里以chain鏈的方式,和stream代碼塊邏輯對應,分步驟展示了每個stream處理環節的執行結果。比如我們以 filter環節為例,窗口中以左右視圖的形式,左側顯示了原始輸入的內容,右側是經過filter處理后符合條件并保留下來的數據內容,并且還有連接線進行指引,一眼就可以看出哪些元素是被過濾舍棄了的:

      不止于此,Stream Trace除了提供上述分步查看結果的能力,還支持直接顯示整體的鏈路執行全貌。點擊Stream Trace窗口左下角的 Flat Mode按鈕即可切換到全貌模式,可以看到最初原始數據,如何一步步被處理并得到最終的結果。

      看到這里,以后還會說Stream不好調試嗎?至少我不會了。

      小心Collectors.toMap出現key值重復報錯

      在我們常規的HashMap的 put(key,value)操作中,一般很少會關注key是否已經在map中存在,因為put方法的策略是存在會覆蓋已有的數據。但是在Stream中,使用 Collectors.toMap方法來實現的時候,可能稍不留神就會踩坑。所以,有小伙伴在評論區熱心的提示,在使用此方法的時候需要手動加上 mergeFunction以防止key沖突。

      這個究竟是怎么回事呢?我們看下面的這段代碼:

      public void testCollectStopOptions() {    List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(22));    // collect成HashMap,key為id,value為Dept對象    Map<Integer, Dept> collectMap = ids.stream()            .collect(Collectors.toMap(Dept::getId, dept > dept));    System.out.println(&34;collectMap:&34; + collectMap);}

      執行上述代碼,不出意外的話會出意外。如下結果:

      Exception in thread &34;main&34; java.lang.IllegalStateException: Duplicate key Dept{id=22}at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)at java.util.HashMap.merge(HashMap.java:1254)at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

      因為在收集器進行map轉換的時候,由于出現了重復的key,所以拋出異常了。 為什么會出現異常呢?為什么不是以為的覆蓋呢?我們看下源碼的實現邏輯:

      可以看出,默認情況下如果出現重復key值,會對外拋出IllegalStateException異常。同時,我們看到,它其實也有提供重載方法,可以由使用者自行指定key值重復的時候的執行策略:

      所以,我們的目標是出現重復值的時候,使用新的值覆蓋已有的值而非拋出異常,那我們直接手動指定下讓toMap按照我們的要求進行處理,就可以啦。改造下前面的那段代碼,傳入自行實現的 mergeFunction函數塊,即指定下如果key重復的時候,以新一份的數據為準:

          public void testCollectStopOptions() {        List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(22));        // collect成HashMap,key為id,value為Dept對象        Map<Integer, Dept> collectMap = ids.stream()                .collect(Collectors.toMap(                        Dept::getId,                        dept > dept,                        (exist, newOne) > newOne));        System.out.println(&34;collectMap:&34; + collectMap);    }

      再次執行,終于看到我們預期中的結果了:

      collectMap:{17=Dept{id=17}, 22=Dept{id=22}}

      By The Way,個人感覺JDK在這塊的默認實現邏輯有點不合理。雖然現在默認的拋異常方式,可以強制讓使用端感知并去指定自己的邏輯,但這默認邏輯與map的put操作默認邏輯不一致,也讓很多人都會無辜踩坑。如果將默認值改為有則覆蓋的方式,或許會更符合常理一些 —— 畢竟被廣泛使用的HashMap的源碼里,put操作默認就是覆蓋的,不信可以看HashMap源碼的實現邏輯:

      慎用peek承載業務處理邏輯

      peek和 foreach在Stream流操作中,都可以實現對元素的遍歷操作。區別點在與peek屬于中間方法,而foreach屬于終止方法。這也就意味著peek只能作為管道中途的一個處理步驟,而沒法直接執行得到結果,其后面必須還要有其它終止操作的時候才會被執行;而foreach作為無返回值的終止方法,則可以直接執行相關操作。

      那么,只要有終止方法一起,peek方法就一定會被執行嗎?非也!看版本、看場景! 比如在 JDK1.8版本中,下面這段代碼中的peek方法會正常執行,但是到了 JDK17中就會被自動優化掉而不執行peek中的邏輯:

          public void testPeekAndforeach() {        List<String> sentences = Arrays.asList(&34;hello world&34;, &34;Jia Gou Wu Dao&34;);        sentences.stream().peek(sentence > System.out.println(sentence)).count();    }

      至于原因,可以看下JDK17官方API文檔中的描述:

      因為對于 findFirst、count之類的方法,peek操作被視為與結果無關聯的操作,直接被優化掉不執行了。所以說最好按照API設計時預期的場景去使用API,避免自己給自己埋坑。

      我們從peek的源碼的注釋上可以看出,peek的推薦使用場景是用于一些調試場景,可以借助peek來將各個元素的信息打印出來,便于開發過程中的調試與問題定位分析。

      我們再看下peek這個詞的含義解釋:

      既然開發者給它起了這么個名字,似乎確實僅是為了窺視執行過程中數據的變化情況。為了避免讓自己踩坑,最好按照設計者推薦的用途用法進行使用,否則即使現在沒問題,也不能保證后續版本中不會出問題。

      字符串拼接明明有join,那么Stream中Collectors.join存在意義是啥

      在介紹Stream流的收集器時,有介紹過使用 Collectors.joining來實現多個字符串元素之間按照要求進行拼接的實現。比如將給定的一堆字符串用逗號分隔拼接起來,可以這么寫:

          public void testCollectJoinStrings() {        List<String> ids = Arrays.asList(&34;AAA&34;, &34;BBB&34;, &34;CCC&34;);        String joinResult = ids.stream().collect(Collectors.joining(&34;,&34;));        System.out.println(joinResult);    }

      有很多同學就提出字符串元素拼接直接用 String.join就可以了,完全沒必要搞這么復雜。

      如果是純字符串簡單拼接的場景,確實直接String.join會更簡單一些,這種情況下使用Stream進行拼接的確有些大材小用了。 但是 joining的方法優勢要體現在Stream體系中,也就是與其余Stream操作可以結合起來綜合處理。String.join對于簡單的字符串拼接是OK的,但是如果是一個Object對象列表,要求將Object某一個字段按照指定的拼接符去拼接的時候,就力不從心了——而這就是使用 Collectors.joining的時機了。比如下面的實例:

      小結

      好啦,關于Java Stream相關的內容點的補充,就聊到這里啦。如果需要全面了解Java Stream的相關內容,可以看我此前分享的文檔。那么,你對Java Stream是否還有哪些疑問或者自己的獨特理解呢?歡迎一起交流下。

      傳送門:

      • 全面吃透JAVA Stream流操作,讓代碼更加的優雅
      • 講透JAVA Stream的collect用法與原理,遠比你想象的更強大

      我是悟道,聊技術、又不僅僅聊技術~

      如果覺得有用,請點贊 + 關注讓我感受到您的支持。也可以關注下我的公眾號【架構悟道】,獲取更及時的更新。

      期待與你一起探討,一起成長為更好的自己。

      主站蜘蛛池模板: 亚洲日韩精品无码专区加勒比| 特黄一级视频| 国产不卡一区二区在线| 黑色丝袜脚足国产在线看| 狠狠躁夜夜躁无码中文字幕| 久久人人玩人妻潮喷内射人人| 日韩一级黄色| www.天堂av.com| 18禁止看的免费污网站| 在线无码免费网站永久| 在线观看视频亚洲| 中文字幕日韩精品亚洲一区| 国产真人作爱免费视频道歉 | 国产熟妇乱子伦视频在线观看| 人妻无码专区一区二区三区| 国产区一区| 毛茸茸亚洲孕妇孕交片| 巨胸喷奶水www视频网站| 成人午夜精品| 88国产精品视频一区二区三区| 男人扒开女人内裤强吻桶进去| 永久免费无码日韩视频| 超碰成人网| 男女午夜视频在线观看| 98久9在线 | 视频| a国产在线v的不卡视频| 中文字幕视频在线播放| 精品国产无码一区二区三区| 伊人久久婷婷五月综合97色| 综合色就爱涩涩涩综合婷婷| 在线看免费| 免费毛片视频网站| 伦人伦xxx国产对白| 四川老熟女下面又黑又肥| 看看黄色片| 蜜桃一二三区| 国产一区二区精品久久| 亚洲国产欧美在线观看的| 你懂的网址在线观看| 麻豆国产一区二区| 久久国产自偷自偷免费一区调|