Kai Chen
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 六角鼠年鐵人賽 Week 17 - Spring Boot - 番外篇 Java 8 Stream Tutorial ==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai== ## 劇中佳句 讓子彈飛 :::info 世界上本沒有路,有了腿便有了路 ::: ## 主題 承上周介紹了 **Lambda** 後,今天繼續介紹另外一個歷史級的 feature -- **Stream** **Stream** 是 JDK 8 版本提供用來處理 Collections 物件的一套特殊處理機制,同時也是將 Java 原先的迭代處理方式由 **外部迭代**,改為 **內部迭代** 的重要推手。 搭配上 Lambda 表示式,讓開發人員在寫 code 上的發揮如虎添翼,省去了許多繁文縟節,可以更專心地在刻劃業務邏輯上,重複性質的過程盡可能的避免,大大的提升了時程上的效益,也讓 Java 在開發上終於不再被太多的 **前處理** 綁住,逐漸看齊新興高階語言的流暢。 ## 迭代 先補上一個簡單的架構圖,以便清楚明瞭何謂外部迭代、內部迭代。 ![](https://i.imgur.com/zJzLbwy.png) 在介紹 Stream 前,首先要了解的是關於 Java 的迭代,也就是平時工程師在處理 Collection 物件時候會需要用到的 **迴圈**。 > **小知識** 遞迴與迭代? > 迭代演算法 >> 指的是那些經由重複性運算處理,得以讓變數的原值每經過一次運算後能夠得到新值的方法。 >> > 遞迴 >> 是設計與描述演算法的工具,常在使用演算法前被拿出做討論,而能夠採用遞迴的演算法通常具備幾種特質: >> 1. 集合能夠被拆解為許多小集合 >> 2. 小集合的解匯集後可以成為大集合的解 >> 3. 當一個集合無法被拆分成更多的小集合時則視為個體物件,將執行運算流程後返回解,並提供給上一層的集合,以讓其進行運算 >> 4. 每一個集合體(無論大、小、個體物件),都能夠適用於同一種分解和運算流程。 Java 中常見的 **外部迭代** 莫過於 **For Loop**、**For Each**、**While**、**Do While**,而在使用這些 function 的時候,開發人員還是得自己刻畫其邏輯架構、判斷依據、輪迴方式等,其實對於開發人員來說,最重要的莫過於下列的幾點: - 傳入的物件 - 限制條件 - 業務邏輯處理 - 輸出結果 也許有人會說,其實我已經把所有的東西都說出來了不是嗎? 且聽後續娓娓道來,慢慢讓你體會 Stream 的便利之處。 ## Stream 是一種 Collection? Stream 是 Collections Interface 底下的新規格,也就是說所有繼承、實作 Collections 的類別皆有支援。 在上篇 Lambda 表示式的文章內有提到,Java 8 後的版本可以在 Interface 增加 default 的方式,這讓 Stream 可以無痛相容所有 Java 7 的 Collections 類別。 **Collections Interface Content:** ```java= default Stream<E> stream( ) { return StreamSupport.stream(spliterator(), false); } ``` 在應用上,許多我們熟知的 Collections 類別都可以轉成 Stream,舉例: ```java= // List 轉 Stream List.stream() // Set 轉 Stream Set.stream(); // Map 轉 Stream,需要先轉成 Set Map.entrySet().stream(); // Object [] 轉 Stream Arrays.stream(Object[]); ``` 當然反面思考,我們也可以將 Stream 轉為特定的集合物件: ```java= // Stream 轉為 List stream.collect(Collectors.toList()); // Stream 轉為 Set stream.collect(Collectors.toSet()); // Stream 轉為 Map stream.collect(Collectors.toMap(e -> e[0], e -> e[1])); // Stream 轉為 Object [] stream.toArray(size -> new Object[size]); ``` 這種通用性讓我們可以放心的使用 Stream 去處理資料集合,並且能夠把資料集合轉為我們希望的集合型態再做處理。 ## Stream 的邏輯架構 Stream 的方法中各類操作都有其性質,這些性質可分成兩類: - 惰性 (Lazy) - 急性 (Eager) 多數 Stream 的操作都屬於惰性,這表示在進行這些操作時,Stream 並沒有真正的處理資料集合,只是被賦予了許多設定,被告知該如何處理,但不會真正去執行。 要真正讓 Stream 運作起來,必須要在最後設一個急性操作,方能將前面的惰性操作從設定轉為實際處理,這就像是烹飪一道菜,你可以說得滿嘴食材、醬料、調理方式甚至是最後的擺盤等等,但真正推動你下廚房的理由,可能是準備一份晚餐又或者是準備一場約會。 惰性操作種類繁多,通常定義在急性操作前,主要用於**查找**、**分類**、**排序**或**集成Map**等設定,注意!! 這邊都只是設定,必須要在後方搭配一個急性操作,才能將惰性設定轉為實際處理動作,惰性方法舉例如下: | 方法種類 | 方法 | | --- | --- | |**Search Function**|filter, findFirst, findAny, anyMatch, allMatch, distinct| |**Sort Function**|sorted, limit| |**Map Function**|flapMap, Map| 急性操作種類和方法較少,主要進行最後**集成**、**轉型**或**邏輯結果處理**等操作,急性方法舉例如下: | 方法種類 | 方法 | | --- | --- | |**Collect Function**|collect| |**Loop Function**|forEach| |**Logic Function**|if...系列| ## 實際範例 這裡直接展示一個實際的外部迭代和 Stream 版本的內部迭代的差異。 Kai 接下來的範例會建立一個關於歌手和歌曲的物件,並在製成幾個範例物件後裝入 Collection 進行迭代的輸入和輸出。 歌手的物件包含下列資訊: - 歌手名稱 - 歌曲數量 - 曲風 - 專輯主要流行地區 歌曲的物件包含下列資訊: - 歌曲名稱 - 專輯名稱 - 歌曲時間長度 - 以秒為單位 - 作詞者 開始建立歌手和歌曲資訊的物件 **Singer.java** ```java= package kai.com.stream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.util.List; @Getter @Setter @AllArgsConstructor @ToString public class Singer { private String name; private int countMusic; private String type; private String country; private List<Song> songs; } ``` **Song.java** ```java= package kai.com.stream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Getter @Setter @AllArgsConstructor @ToString public class Song { String album; String name; int songSeconds; String lyricist; } ``` > Kai 這邊搭配 Lombok 套件處理建構子、Setter、Getter 和 toString() 部分 > 不熟悉的朋友可以搭配參考 [六角鼠年鐵人賽 Week 11 - Spring Boot - Lombok 省時省力好幫手](/uWHqezBxQ9CPqr5iUOfNFQ?both) 這一篇文章 接著這一單元的主程式 **StreamTest.java** ```java= package kai.com.stream; import java.util.HashMap; import java.util.Map; public class StreamTest { public static void main(String [] args){ // 建立範例 Map Map<Integer, Singer> map = new HashMap<>(); // 建立歌曲物件 Song song_s1_1 = new Song("Beat It","Thriller",259,"Michael Jackson"); Song song_s1_2 = new Song("Billie Jean","Thriller",293,"Michael Jackson"); Song song_s2_1 = new Song("Bad Day","Bad Day",234,"Daniel Powter"); Song song_s2_2 = new Song("Do You Wanna Get Lucky","Holiday Version",218,"Daniel Powter"); Song song_s3_1 = new Song("Blue and White Porcelain","On the Run",243,"Vincent Fang"); // 青花瓷, 我很忙, 方文山 Song song_s3_2 = new Song("Love Confession","Jay Chou's Bedtime Stories",215,"Jay Chou"); // 告白氣球, 周杰倫的床邊故事, 周杰倫 Song song_s4_1 = new Song("UGLY BEAUTY","UGLY BEAUTY",295,"Wu QingFeng"); // 怪美的, 怪美的, 吳青峰 Song song_s4_2 = new Song("The Great Artist","Muse",271,"Matthew Yen"); // 大藝術家, Muse, 嚴云農 Song song_s5_1 = new Song("LIGHT YEARS AWAY","Passengers",235,"G.E.M"); // 光年之外, Passengers, 鄧紫棋 Song song_s5_2 = new Song("City Zoo",null,284,"G.E.M"); // 建立歌手物件並放入歌曲物件 Singer s1 = new Singer("Michael Jackson",100,"All","USA", Arrays.asList(song_s1_1,song_s1_2)); Singer s2 = new Singer("Daniel Powter",20,"Life Romantic","USA",Arrays.asList(song_s2_1,song_s2_2)); Singer s3 = new Singer("Jay Chou",60,"POP Music","Taiwan", Arrays.asList(song_s3_1,song_s3_2)); // 周杰倫 Singer s4 = new Singer("Jolin Tsai",60,"POP Music","Taiwan",Arrays.asList(song_s4_1,song_s4_2)); // 蔡依林 Singer s5 = new Singer("G.E.M",40,"POP Music","China",Arrays.asList(song_s5_1,song_s5_2)); // 鄧紫棋 // 將歌手物件放入 Map map.put(1,s1); map.put(2,s2); map.put(3,s3); map.put(4,s4); map.put(5,s5); } } ``` ### 1. 單純的 Print - 傳統的外部迭代 ```java= for(Map.Entry<Integer,Singer> s:map.entrySet()){ System.out.println(s.toString()); } ``` 輸出成果: ``` Tranditional Iterate: 1=Singer(name=Michael Jackson, countMusic=100, type=All, country=USA) 2=Singer(name=Daniel Powter, countMusic=20, type=Life Romantic, country=USA) 3=Singer(name=Jay Chou, countMusic=60, type=POP Music, country=Taiwan) 4=Singer(name=Jolin Tsai, countMusic=60, type=POP Music, country=Taiwan) 5=Singer(name=G.E.M, countMusic=40, type=POP Music, country=China) ``` - Stream 版的內部迭代 ```java= map.entrySet().stream() .forEach(s -> System.out.println(s.toString())); ``` 輸出成果: ``` Stream Iterate 1=Singer(name=Michael Jackson, countMusic=100, type=All, country=USA) 2=Singer(name=Daniel Powter, countMusic=20, type=Life Romantic, country=USA) 3=Singer(name=Jay Chou, countMusic=60, type=POP Music, country=Taiwan) 4=Singer(name=Jolin Tsai, countMusic=60, type=POP Music, country=Taiwan) 5=Singer(name=G.E.M, countMusic=40, type=POP Music, country=China) ``` 目前來說上述還看不太出來 Stream 的優勢,我們繼續往下做一些分類和處理 ### 2. 篩選 USA 的歌手物件 - 傳統的外部迭代 ```java= for(Map.Entry<Integer,Singer> s:map.entrySet()){ if(s.getValue().getCountry().equals("USA")) System.out.println(s.toString()); } ``` 輸出結果: ``` Tranditional Iterate: 1=Singer(name=Michael Jackson, countMusic=100, type=All, country=USA, songs=[Song(album=Beat It, name=Thriller, songSeconds=259, lyricist=Michael Jackson), Song(album=Billie Jean, name=Thriller, songSeconds=293, lyricist=Michael Jackson)]) 2=Singer(name=Daniel Powter, countMusic=20, type=Life Romantic, country=USA, songs=[Song(album=Bad Day, name=Bad Day, songSeconds=234, lyricist=Daniel Powter), Song(album=Do You Wanna Get Lucky, name=Holiday Version, songSeconds=218, lyricist=Daniel Powter)]) ``` - Stream 版的內部迭代 ```java= map.entrySet().stream() .filter(s -> "USA".equals(s.getValue().getCountry())) .forEach(s -> System.out.println(s.toString())); ``` 輸出結果: ``` Stream Iterate 1=Singer(name=Michael Jackson, countMusic=100, type=All, country=USA, songs=[Song(album=Beat It, name=Thriller, songSeconds=259, lyricist=Michael Jackson), Song(album=Billie Jean, name=Thriller, songSeconds=293, lyricist=Michael Jackson)]) 2=Singer(name=Daniel Powter, countMusic=20, type=Life Romantic, country=USA, songs=[Song(album=Bad Day, name=Bad Day, songSeconds=234, lyricist=Daniel Powter), Song(album=Do You Wanna Get Lucky, name=Holiday Version, songSeconds=218, lyricist=Daniel Powter)]) ``` ### 3. 篩選歌曲秒數在 250 秒以上並列成集合物件 - 傳統的外部迭代 ```java= List<Song> targetSongList = new ArrayList<>(); for(Map.Entry<Integer,Singer> s:map.entrySet()){ List<Song> songList = s.getValue().getSongs(); for(Song song: songList){ if(song.getSongSeconds() > 250) targetSongList.add(song); } } for(Song s:targetSongList){ System.out.println(" - " + s.getName()); } ``` 輸出結果: ``` Tranditional Iterate: - Beat It - Billie Jean - UGLY BEAUTY - The Great Artist - City Zoo ``` - Stream 版的內部迭代 ```java= List<Song> targetSongList2 = map.entrySet().stream() .flatMap(s -> s.getValue().getSongs().stream()) .filter(s -> s.getSongSeconds() > 250) .collect(Collectors.toList()); targetSongList2.stream() .forEach(s -> System.out.println(" - " + s.getName())); ``` 輸出結果: ``` Stream Iterate - Beat It - Billie Jean - UGLY BEAUTY - The Great Artist - City Zoo ``` ### 4. 呈上題,將輸出的歌曲集合作名稱 A - Z 排列 - 傳統的外部迭代 你會發現用傳統的外部迭代方式會很難去對 ArrayList 做排序,你可能需要去實作些特別的判斷式才能完成這件事情。 有興趣可以參考這一篇 [Java School 教 ArrayList 的三種排序方式](http://www.51gjie.com/java/647.html) - Stream 版的內部迭代 ```java= // 以原先的流程來說,只需要多加一行 sorted() 即可處理 targetSongList2.stream() .sorted(Comparator.comparing(Song::getName)) .forEach(s -> System.out.println(" - " + s.getName())); ``` 輸出結果: ``` Stream Iterate - Beat It - Billie Jean - City Zoo - The Great Artist - UGLY BEAUTY ``` ## 統整優劣與結論 從上述範例可以發現到 Stream 的幾個優缺點,例如語法可以較有條理,但是因為無法看到內部迭代的狀況導致 Debug 時候會較以往來的困難等。 但無論如何,Stream 的出現仍然大幅地變動了原先繁雜的 Java 迭代程序,與時俱進的 Java 程式設計師,這是無論如何都必須學會的東西! 統整優缺點如下: |優點 |缺點| |--- |---| |語法清晰較有條理 |須個別了解集合物件內容的資料 | |處理程序能夠一眼看出 |Debug Mode 不好理解迭代中的處理狀況| |省略許多繁雜的重複語法 || |迭代功能齊全|| ## 結語 :::danger 還有許多 Stream 特別的功能 Kai 並沒有全部提到,有興趣的朋友們可以透過 Kai 簡單的介紹後,在實作中一一去接觸~ 目前來說應該只有這兩篇番外是關於 Java 8 變動較大的部分,另外 Kai 還在思考是否寫一篇關於 Optional API 的部分,但相比較前兩篇來說,這個部分算是比較小的變動...。 Anyway,下篇文章便會揭曉XD [六角鼠年鐵人賽 Week 18 - Spring Boot - 番外篇 Java 8 Optional Tutorial](/jBlOCstVT3W0iG9KgSZ9Jg) ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`,`w3HexSchool`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully