巧用Java8中的Stream,讓集合操作飛起來!

2020-12-11 酷扯兒

本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫

作者:堅持就是勝利

juejin.im/post/5d5e2616f265da03b638b28a

簡介

java8也出來好久了,接口默認方法,lambda表達式,函數式接口,Date API等特性還是有必要去了解一下。比如在項目中經常用到集合,遍歷集合可以試下lambda表達式,經常還要對集合進行過濾和排序,Stream就派上用場了。用習慣了,不得不說真的很好用。

Stream作為java8的新特性,基於lambda表達式,是對集合對象功能的增強,它專注於對集合對象進行各種高效、便利的聚合操作或者大批量的數據操作,提高了編程效率和代碼可讀性。

Stream的原理:將要處理的元素看做一種流,流在管道中傳輸,並且可以在管道的節點上處理,包括過濾篩選、去重、排序、聚合等。元素流在管道中經過中間操作的處理,最後由最終操作得到前面處理的結果。

集合有兩種方式生成流:

stream() 為集合創建串行流parallelStream() - 為集合創建並行流

上圖中是Stream類的類結構圖,裡面包含了大部分的中間和終止操作。

中間操作主要有以下方法(此類型方法返回的都是Stream):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

終止操作主要有以下方法:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

舉例說明

首先為了說明Stream對對象集合的操作,新建一個Student類(學生類),覆寫了equals()和hashCode()方法

public class Student {private Long id; private String name; private int age; private String address; public Student() {} public Student(Long id, String name, int age, String address) { this.id = id; this.name = name; this.age = age; this.address = address; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && Objects.equals(id, student.id) && Objects.equals(name, student.name) && Objects.equals(address, student.address); } @Override public int hashCode() { return Objects.hash(id, name, age, address); } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; }}

filter(篩選)

public static void main(String [] args) {Student s1 = new Student(1L, "肖戰", 15, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); List<Student> streamStudents = testFilter(students); streamStudents.forEach(System.out::println); } /** * 集合的篩選 * @param students * @return */ private static List<Student> testFilter(List<Student> students) { //篩選年齡大於15歲的學生// return students.stream().filter(s -> s.getAge()>15).collect(Collectors.toList()); //篩選住在浙江省的學生 return students.stream().filter(s ->"浙江".equals(s.getAddress())).collect(Collectors.toList()); }

運行結果:

這裡我們創建了四個學生,經過filter的篩選,篩選出地址是浙江的學生集合。

map(轉換)

public static void main(String [] args) { Student s1 = new Student(1L, "肖戰", 15, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); testMap(students); } /** * 集合轉換 * @param students * @return */ private static void testMap(List<Student> students) { //在地址前面加上部分信息,只獲取地址輸出 List<String> addresses = students.stream().map(s ->"住址:"+s.getAddress()).collect(Collectors.toList()); addresses.forEach(a ->System.out.println(a)); }

運行結果

map就是將對應的元素按照給定的方法進行轉換。

distinct(去重)

public static void main(String [] args) { testDistinct1(); } /** * 集合去重(基本類型) */ private static void testDistinct1() { //簡單字符串的去重 List<String> list = Arrays.asList("111","222","333","111","222"); list.stream().distinct().forEach(System.out::println); }

public static void main(String [] args) {testDistinct2(); } /** * 集合去重(引用對象) */ private static void testDistinct2() { //引用對象的去重,引用對象要實現hashCode和equal方法,否則去重無效 Student s1 = new Student(1L, "肖戰", 15, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); Student s5 = new Student(1L, "肖戰", 15, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.add(s5); students.stream().distinct().forEach(System.out::println); }

可以看出,兩個重複的「肖戰」同學進行了去重,這不僅因為使用了distinct()方法,而且因為Student對象重寫了equals和hashCode()方法,否則去重是無效的。

sorted(排序)

public static void main(String [] args) { testSort1(); } /** * 集合排序(默認排序) */ private static void testSort1() { List<String> list = Arrays.asList("333","222","111"); list.stream().sorted().forEach(System.out::println); }

public static void main(String [] args) { testSort2(); } /** * 集合排序(指定排序規則) */ private static void testSort2() { Student s1 = new Student(1L, "肖戰", 15, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.stream() .sorted((stu1,stu2) ->Long.compare(stu2.getId(), stu1.getId())) .sorted((stu1,stu2) -> Integer.compare(stu2.getAge(),stu1.getAge())) .forEach(System.out::println); }

上面指定排序規則,先按照學生的id進行降序排序,再按照年齡進行降序排序

limit(限制返回個數)

public static void main(String [] args) { testLimit(); } /** * 集合limit,返回前幾個元素 */ private static void testLimit() { List<String> list = Arrays.asList("333","222","111"); list.stream().limit(2).forEach(System.out::println); }

skip(刪除元素)

public static void main(String [] args) { testSkip(); } /** * 集合skip,刪除前n個元素 */ private static void testSkip() { List<String> list = Arrays.asList("333","222","111"); list.stream().skip(2).forEach(System.out::println); }

reduce(聚合)

public static void main(String [] args) { testReduce(); } /** * 集合reduce,將集合中每個元素聚合成一條數據 */ private static void testReduce() { List<String> list = Arrays.asList("歡","迎","你"); String appendStr = list.stream().reduce("北京",(a,b) -> a+b); System.out.println(appendStr); }

min(求最小值)

public static void main(String [] args) { testMin(); } /** * 求集合中元素的最小值 */ private static void testMin() { Student s1 = new Student(1L, "肖戰", 14, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); Student minS = students.stream().min((stu1,stu2) ->Integer.compare(stu1.getAge(),stu2.getAge())).get(); System.out.println(minS.toString()); }

上面是求所有學生中年齡最小的一個,max同理,求最大值。

anyMatch/allMatch/noneMatch(匹配)

public static void main(String [] args) { testMatch(); } private static void testMatch() { Student s1 = new Student(1L, "肖戰", 15, "浙江"); Student s2 = new Student(2L, "王一博", 15, "湖北"); Student s3 = new Student(3L, "楊紫", 17, "北京"); Student s4 = new Student(4L, "李現", 17, "浙江"); List<Student> students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); students.add(s4); Boolean anyMatch = students.stream().anyMatch(s ->"湖北".equals(s.getAddress())); if (anyMatch) { System.out.println("有湖北人"); } Boolean allMatch = students.stream().allMatch(s -> s.getAge()>=15); if (allMatch) { System.out.println("所有學生都滿15周歲"); } Boolean noneMatch = students.stream().noneMatch(s -> "楊洋".equals(s.getName())); if (noneMatch) { System.out.println("沒有叫楊洋的同學"); } }

anyMatch:Stream 中任意一個元素符合傳入的 predicate,返回 true

allMatch:Stream 中全部元素符合傳入的 predicate,返回 true

noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true

總結

上面介紹了Stream常用的一些方法,雖然對集合的遍歷和操作可以用以前常規的方式,但是當業務邏輯複雜的時候,你會發現代碼量很多,可讀性很差,明明一行代碼解決的事情,你卻寫了好幾行。試試lambda表達式,試試Stream,你會有不一樣的體驗。

相關焦點

  • JAVA8——JAVA成長之路
    在最簡單的形式中,一個lambda可以由用逗號分隔的參數列表、–>符號與函數體三部分表示。例如:請注意參數e的類型是由編譯器推測出來的。Stream API極大簡化了集合框架的處理(但它的處理的範圍不僅僅限於集合框架的處理,這點後面我們會看到)。
  • 黑客日教程-JAVA8 STREAM SKIP() VS LIMIT(),對STREAM切片
    儘管這兩個操作最初看起來非常相似,但它們實際上表現得非常不同並,且不可互換。實際上,它們是互補的,並且在一起使用時可以很方便。2. skip()方法skip方法是一個中間操作,跳過stream中的前n個元素,n不能為負值。如果n大於stream的大小,則返回空stream。
  • 學起來:生活中巧用調味品的小妙招
    這裡,我們通過《秘制調味料》這本書,來學一學生活中巧用調味品的小妙招。鮮嫩的食材要儘量少用調味品新鮮、醇香的食材,要儘量少用調味品,比如應季的蔬菜、瓜果、新鮮的魚肉等等,這些食材,如果用過多調味品,尤其是辛香料之類,就會掩蓋食材本身自帶的鮮味,甚至會影響菜餚的口感。
  • java8的方法引用詳解(讓你的代碼看起來高大上) - 愚公要移山1
    在上一篇文章中曾經介紹到了java8中的Lambda表達式,這篇文章是基於Lambda表達式來敘述的。主要講解方法引用。在此之前還希望你能夠了解一下函數式接口的使用。一、什麼是方法引用在Lambda表達式中,將其劃分了幾塊。這一行就是lambda表達式。
  • 巧攝即將推出重磅新功能
    地面看不過癮,飛高看看。陳聲老師成天天上飛,你如果沒法飛就在巧攝裡面看吧。 你就去搜索名字就好了,如果找到你想要的模型,你下載skp文件,然後用Sketchup軟體打開,導出為obj文件,拷貝到巧攝下面的models目錄下面,然後就可以導入到巧攝裡面了。我後面會錄一個視頻,演示給大家看。整個過程其實不算複雜。不過如果你沒有Sketchup(這個軟體是收費軟體)或者對電腦操作和三維建模不熟悉,這條路就行不通了。這也決定了絕大多數巧攝用戶都沒法用這個辦法。
  • 巧用2個操作,就能瞬間找到!
    為了讓小夥伴們更好地了解遊戲中的內容,希望大家能動動小手關注一下。本期內容去往末地擊殺了末影龍後,在周圍可以找到折躍門可以進入外島。之所以要去往末地外島,是因為這裡有著末地城的存在。很多非酋玩家剛進入外島時,通過徒步很難找到末地城的蹤跡,這時候巧用2個操作,就能瞬間找到。
  • 飛起來了!滑板車飛起來了!
    飛起來了!滑板車飛起來了!哎呀,放錯圖了那如果滑板車能飛,是不是更酷了?其設計理念突破了傳統有人駕駛飛行器的局限性,持續貫徹「全備份安全設計,自動駕駛,智慧指揮調度中心集群控制」等三大設計理念,並在研發過程中持續優化提升產品性能,適應更廣泛的應用需求。一直以來,三發公司都致力於打造安全、環保、智能的低空自動駕駛載人飛行器,為未來的智慧交通提供低空中短途交通運輸解決方案。
  • QGhappy.飛牛天秀操作再現冠軍風採 暴力花木蘭主宰戰場
    眾所周知,飛牛最喜愛的英雄是花木蘭,花木蘭也給飛牛帶來了冠軍榮耀,飛牛與花木蘭之間的聯繫在大家看來早已密不可分。近日飛牛在直播間排位上王者的路上,就又拿花木蘭大秀了一場,純輸出出裝最終打出12-3-9的豪華數據主宰戰場,最後更是用一波天秀操作打出三連決勝帶領隊友團滅敵軍贏得勝利,再現當年冠軍飛將的風採。
  • 《動物派對》飛踢如何操作 飛踢動作操作技巧分享
    導 讀 動物派對手遊裡飛踢怎麼操作呢?
  • 初識Spring Cloud Stream,什麼是消息驅動微服務框架
    如上圖所示,有時候系統中的某個服務會因為用戶操作或內部行為發布一個事件,該服務知道這個事件在將來的某一個時間點會被其他服務所消費,但是並不知道這個服務具體是誰、也不關心什麼時候被消費。spring cloud stream是一個構建與Spring Boot和Spring Integration之上的框架,方便開發人員快速構建基於Message-Driven的系統。
  • Java中的IO與NIO
    字符流(character stream)為字符的輸入和輸出提供了方便,採用了統一的編碼標準,因而可以國際化。值得注意的,在計算機的底層實現中,所有的輸入輸出其實都是以字節的形式進行的,字符流只是為字符的處理提供了具體的方法。
  • 集合競價這個徵兆說明了一切,拒絕盲目操作
    開盤前,早盤集合競價究竟有什麼用?  相信有很多人不知道集合競價有什麼用?  1】 選股方法:9.20分,通達信軟體點60,出現集合競價第一版的漲幅榜,在9.25分後把第一版中沒漲停的保存到自定義的文件夾裡,如果第一版中的票很少,就把第二版中2個點以上的都加進來  2】打開自定義板塊中剛選出的票【自定義可以用當天的日期來取名】,然後,先把股價過高的刪掉,再把漲幅過高的刪除掉,以保證當天買入後獲取更多的利潤空間, 先點量比排行,再點成交量排行,把量比和成交量都排在前邊的票作為首選
  • 巧了巧了,實在太巧了
    巧了巧了,實在太巧了 人類第一次用降落傘登月成功! 這老頭可真壞,把裙子都給人家吹飛了 ▼
  • Python數據類型之集合set
    # s = {"123","qwe"}# 創建一個空集合必須用 set() 而不是 { },因為 { } 是用來創建一個空字典# s = {},這種是創建空的字典# 集合的兩大功能:# 集合可以實現去重的功能
  • 巧用拼圖遊戲 讓寶寶快樂學英語
    新東方網>學前>幼兒教育>趣味學習樂園>學前班英語>少兒英語>正文巧用拼圖遊戲 讓寶寶快樂學英語 2013-02-01 11:07 來源:滬江英語
  • Java 8 中的 Map 騷操作,學習下!
    怎麼用? 簡介 使用場景 其他 總結 前段時間無意間發現了方法,感覺還是很好用的,此文簡單做一些相關介紹。首先我們先看一個例子。 怎麼用? 假設我們有這麼一段業務邏輯,我有一個學生成績對象的列表,對象包含學生姓名,科目,科目分數三個屬性,要求求得每個學生的總成績。
  • 集合競價有什麼用?集合競價的時間
    集合競價選股是什麼?集體競價是指在股票的每個交易日上午9:15-9:25,由投資者按照自己能接受的心理價位自由地進行買賣申請。集合競價階段往往隱含著主力資金當日運作意圖的一些信息。因此,投資者認真、細緻地分析集合競價情況,可以及早進入狀態,熟悉最新的交易信息,敏銳發現並能抓住集合競價中出現的某些稍縱即逝的機會。  集合競價的時間  9:15—9:20接受委託並且可以撤銷,9:20—9:25接受委託但不可撤銷。在集合競價時間內的有效委託報單未成交,則自動有效進入9:30開始的連續競價。
  • LOL:機制簡單但操作困難的技能,連職業選手用起來都很吃力
    放到英雄聯盟中,大概可以理解為看別人放技能的時候確實簡單,但是自己玩的時候卻發現完全不是那麼一回事,實踐還是非常重要的。有些英雄的技能,雖然從機制這個角度來看的話,他們非常簡單,沒有什麼離譜的觸發條件,然而真正操作起來卻發現很困難。