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

2020-12-10 酷扯兒

本文轉載自【微信公眾號: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中Stream原理分析
    Java中的Stream並不會向集合那樣存儲和管理元素,而是按需計算數據源流的來源可以是集合Collection、數組Array、I/O channel, 產生器generator 等聚合操作類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等和以前的Collection操作不同, Stream
  • Java 8 中處理集合的優雅姿勢——Stream
    相比之下,關係型資料庫中也同樣有這些操作,但是在Java 8之前,集合和數組的處理並不是很便捷。不過,這一問題在Java 8中得到了改善,Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。本文就來介紹下如何使用Stream。
  • Java 8中處理集合的優雅姿勢——Stream
    Stream 使用一種類似用 SQL 語句從資料庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的代碼。這種風格將要處理的元素集合看作一種流,流在管道中傳輸,並且可以在管道的節點上進行處理,比如篩選,排序,聚合等。
  • Java8新特性Stream
    3.結果操作,結果操作就是終端操作,將整個Stream計算出一個結果,也可以將Stream再次轉化成集合。整個流程如下圖所示:在下面示例中,我們定義了類Person,隨後我們將多個Person對象放入List中。為了體現出Stream聚合操作的優勢,我們用兩種方式對集合內的數據進行篩選,並將結果輸出。篩選的條件為:1.年齡大於40歲的女性。2.年齡從小到大排序。3.前10個Person對象。
  • Java8中的 Stream 那麼強大,那你知道它的原理是什麼嗎?
    Java中的Stream並_不會_向集合那樣存儲和管理元素,而是按需計算數據源流的來源可以是集合Collection、數組Array、I/O channel, 產生器generator 等聚合操作類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等和以前的
  • Java8中Stream詳細用法大全
    s2 = new Student("bb", 20,2);Student s3 = new Student("cc", 10,3);List<Student> list = Arrays.asList(s1, s2, s3);  //裝成listList<Integer> ageList = list.stream
  • 黑客日教程-對比集合的STREAM().FOREACH()和FOREACH()
    1.簡介在java中有多種方式對集合進行遍歷。本教程中將看兩個類似的方法 Collection.stream().forEach()和Collection.forEach()。在大多數情況下,兩者都會產生相同的結果,但是,我們會看到一些微妙的差異。
  • Java Stream
    流水線很多流操作本身會返回一個流,這樣多個操作就可以連結起來,形成一個大的流水線。流水線的操作可以 看作對數據源進行資料庫式查詢。2. 內部迭代與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。
  • 黑客日教程-Java8新功能:將數據集合分組,類似SQL的GROUP BY
    ## 2 GroupingBy收集器 Java8的Stream API允許我們以聲明的方式來處理數據集合。 靜態工廠方法:Collectors.groupingBy(),以及Collectors.groupingByConcunrrent(),給我們提供了類似SQL語句中的"GROUP BY"的功能。
  • Java8 Stream:2萬字20個實例,玩轉集合的篩選、歸約、分組、聚合
    Stream將要處理的元素集合看作一種流,在流的過程中,藉助Stream API對流中的元素進行操作,比如:篩選、排序、聚合等。Stream可以由數組或集合創建,對流的操作分為兩種:終端操作,每個流只能進行一次終端操作,終端操作結束後流無法再次使用。終端操作會產生一個新的集合或值。
  • Java 8的Stream代碼,你能看懂嗎?
    以下為正文:在Java中,集合和數組是我們經常會用到的數據結構,需要經常對他們做增、刪、改、查、聚合、統計、過濾等操作。相比之下,關係型資料庫中也同樣有這些操作,但是在Java 8之前,集合和數組的處理並不是很便捷。不過,這一問題在Java 8中得到了改善,Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。
  • Java學習進階-Stream流
    Stream流不能重複操作.3. Stream流中不能存儲數據.4. Stream流的操作不會影響數據源數據.  Object[] arr = stream.filter(name -> name.startsWith("張")).toArray(); System.out.println(Arrays.toString(arr)); }}收集到集合中- Stream流中提供了一個方法,可以把流中的數據收集到單列集合中 - <R,A> R collect
  • 系統學習Stream,看這篇就夠了!
    連結起來了。下面說下流和集合在使用上的區別。集合,可以隨時取用,但流在創建後只能被使用一次,若重複消費,則會報錯。創建流在對流進行操作之前,我們首先需要獲得一個Stream對象,創建流有以下幾種方式。2.1 集合Collection的默認方法stream(),可以由集合類創建流。
  • Java8 Stream常用API整理(值得收藏)
    流是 Java8 引入的全新概念,它用來處理集合中的數據。眾所周知,集合操作非常麻煩,若要對集合進行篩選、投影,需要寫大量的代碼,而流是以聲明的形式操作集合,它就像 SQL 語句,我們只需告訴流需要對集合進行什麼操作,它就會自動進行操作,並將執行結果交給你,無需我們自己手寫代碼。
  • 基礎篇:JAVA.Stream函數,優雅的數據流操作
    前言平時操作集合數據,我們一般都是for或者iterator去遍歷,不是很好看。java提供了Stream的概念,它可以讓我們把集合數據當做一個個元素在處理,並且提供多線程模式並發流和CompletableFuture的配合使用「關注公眾號,一起交流,微信搜一搜: 潛行前行」1 stream的構造方式stream內置的構造方法public static<T> Stream
  • 手把手帶你體驗Stream流
    上一篇講解到了Lambda表達式的使用《最近學到的Lambda表達式基礎知識》,還沒看的同學可以先去閱讀一下哈~相信也有不少的同學想要知道:Lambda表達式在工作中哪個場景會用得比較多?我理解的Stream流編程就是:某些場景會經常用到操作(求和/去重/過濾….等等),已經封裝好API給你了,你自己別寫了,調我給你提供的API就好了。
  • Node.js中的Stream
    以文件讀寫為例,文件讀寫的時候,stream並不是一次性地把一個文件中的所有內容都讀取到內存中再進行處理(就是再寫入到另外一個文件中),而是一塊數據一塊數據的進行讀取,讀取完一塊數據就處理一塊數據(把這塊數據寫入到另外一個文件中),而不會讓它一直在內存中。相比於傳統方式,使用stream來處理數據,可以高效的使用內存,更有可能來處理大文件。再以網絡數據傳輸(網上看視頻)為例。
  • JAVA8——JAVA成長之路
    在最簡單的形式中,一個lambda可以由用逗號分隔的參數列表、–>符號與函數體三部分表示。例如:請注意參數e的類型是由編譯器推測出來的。Stream API極大簡化了集合框架的處理(但它的處理的範圍不僅僅限於集合框架的處理,這點後面我們會看到)。