Java SE 8新功能介绍:使用Streams API处理集合
使用Java SE 8 Streams的代碼更干凈,易讀且功能強大.....
在“ Java SE 8新功能介紹”系列的這篇文章中,我們將深入解釋和探索代碼,以了解如何使用流遍歷集合,從集合和數組創建流,聚合流值。
在上一篇文章“ Lambda的遍歷,過濾,處理集合和方法增強 ”中; 我已深入探討和探索如何使用lambda表達式和方法引用 遍歷集合,使用謂詞接口過濾它們,在接口中實現默認方法,最后在接口中實現靜態方法 。
- 源代碼托管在我的Github帳戶上:從此處克隆它。
目錄
1.使用流遍歷集合
介紹
Java的collections框架使您可以使用List和Map類的接口以及Arraylist和HashMap類的類來輕松管理應用程序中數據的有序和無序集合。 自從首次引入以來, 收藏框架一直在不斷發展。 在Java SE 8中,我們現在有了一種使用流API 管理 , 遍歷和聚合集合的新方法。 基于集合的流與輸入或輸出流不同。
怎么運行的
相反,這是處理整個數據而不是單獨處理每個項目的新方法。 使用流時,無需擔心循環或遍歷的細節。 您直接從集合創建流對象。 然后,您可以使用它進行各種操作,包括遍歷,過濾和匯總其值。 我將從eg.com.tm.java8.features.stream.traversing項目的Java8Features包中的該示例開始。 在類代碼SequentialStream ,在Java SE 8中,有兩種收集流,稱為順序流和并行流。
List<person> people = new ArrayList<>();people.add(new Person("Mohamed", 69)); people.add(new Person("Doaa", 25)); people.add(new Person("Malik", 6));Predicate<person> pred = (p) -> p.getAge() > 65;displayPeople(people, pred);...........private static void displayPeople(List<person> people, Predicate<person> pred) {System.out.println("Selected:");people.forEach(p -> {if (pred.test(p)) {System.out.println(p.getName());}}); }順序流是兩者中較簡單的一種,就像迭代器一樣,它使您可以一次處理一個集合中的每個項目。 但是語法比以前少了。 在這段代碼中,我創建了一個人數組列表,并轉換為列表。 它具有一個復雜對象的三個實例,一個名為Person的類。 然后,我使用Predicate聲明條件,并顯示僅滿足條件的人員。 在displayPeople()方法的第48至52行中,我遍歷集合,遍歷數據,并一次測試一項。 運行代碼,您應該得到以下結果:
Selected: Mohamed我將向您展示如何使用流對象重構該代碼。 首先,我將注釋掉這些代碼行。 現在,在注釋的代碼下面,我將從集合對象開始。 人。 然后,我將調用一個名為stream的新方法。 與集合本身一樣,流對象也具有通用聲明。 如果從集合中獲取流,則該流中的項目與集合本身的類型相同。 我的集合具有person類的實例,因此流使用相同的泛型類型。
System.out.println("Selected:");// people.forEach(p -> {// if (pred.test(p)) {// System.out.println(p.getName());// }// });people.stream().forEach(p -> System.out.println(p.getName())); }您將流作為方法調用,現在有了一個可以處理的流對象。 我將從對每個方法的四個簡單調用開始,這將需要一個Lamda表達式。 我將傳遞參數。 這就是我這次通過迭代處理的列表中的項目。 然后是Lambda運算符,然后執行該方法。 我將使用簡單的系統輸出,并輸出此人的姓名。 我將保存并運行代碼,并得到結果。 由于不再過濾,因此我將顯示列表中的所有人員。
Selected: Mohamed Doaa Malik現在,一旦有了流,就可以輕松地使用謂詞對象。 當我為每種方法使用并一次處理每個項目時。 我必須顯式調用謂詞的測試方法。 但是,使用流可以調用名為filter的方法。 那需要一個謂詞對象,并且所有謂詞都有一個測試方法,因此它已經知道如何調用該方法。 因此,我將對此代碼進行一些分解。 我將對.forEach()方法的調用向下移動幾行,然后在中間的空行上調用新的filter方法。
people.stream().filter(pred).forEach(p -> System.out.println(p.getName()));filter方法需要謂詞接口的實例。 然后我將傳入謂詞對象。filter方法返回流,但現在返回過濾后的版本,從那里我可以調用forEach()方法。 我將運行代碼,現在我只顯示滿足謂詞條件的集合中的項目。 您可以在流中做更多的事情。 查看Java SE 8 API文檔中有關流的文檔。
Selected: Mohamed您將看到,除了過濾之外,您還可以對流進行聚合并執行各種其他操作。 在結束本演示之前,我想向您展示順序流和并行流之間的非常重要的區別。 Java SE 8中的流API的目標之一是讓您分拆具有多個CPU的系統上的處理。 Java運行時會自動處理這種多CPU處理。 您需要做的就是將順序流轉換為并行流。
有兩種語法上的實現方式。 我將復制我的順序流類。 我將轉到程序包瀏覽器,然后將其復制并粘貼。 我將命名新類ParallelStream 。 我將開設新班。 在此版本中,我將刪除注釋的代碼。 我不再需要了 現在,這里有兩種創建并行流的方法。 一種方法是從集合中調用另一種方法。 代替流,我將調用parallelStream() 。 現在,我有了一個流,該流將自動分解并分配給不同的處理器。
private static void displayPeople(List<person> people, Predicate<person> pred) {System.out.println("Selected:");people.parallelStream().filter(pred).forEach(p -> System.out.println(p.getName()));}我將運行代碼,然后看到它在做同樣的事情,過濾并返回數據。
Selected: Mohamed這是創建并行流的另一種方法。 我將再次調用此stream()方法。 然后從流方法中,我將調用一個名為parallel()的方法,該方法完全相同。 我從順序流開始,最后以并行流結束。 它仍然是一條小溪。 它仍然可以過濾,仍然可以按照與以前完全相同的方式進行處理。 但是現在它將在可能的情況下分解。
people.stream().parallel().filter(pred).forEach(p -> System.out.println(p.getName()));結論
對于何時在順序流上使用并行流沒有明確的規定。 它取決于數據的大小和復雜性以及硬件的功能。 您正在運行的多CPU系統。 我唯一可以給您的建議是嘗試使用您的應用程序和數據。 設置基準,確定運行時間。 使用順序流和并行流,看看哪種效果更好。
2.從集合和數組創建流
介紹
Java SE 8的流API旨在幫助您管理數據集合,即集合框架的成員,例如數組列表或哈希圖。 但是您也可以直接從數組創建流。
怎么運行的
在Java8Features這個項目中,在例如eg.com.tm.java8.features.stream.creating包中,我有一個名為ArrayToStream的類。 在其主要方法中,我創建了一個包含三個項目的數組。 它們分別是我的復雜對象Person類的實例。
public static void main(String args[]) {Person[] people = {new Person("Mohamed", 69),new Person("Doaa", 25),new Person("Malik", 6)};for (int i = 0; i < people.length; i++) {System.out.println(people[i].getInfo());} }此類具有專用字段的setter和getter以及新的getInfo()方法,以返回串聯的字符串。
public String getInfo() {return name + " (" + age + ")"; }現在,如果您想使用流來處理此數組,您可能會認為可能需要將其轉換為數組列表,然后再從那里創建該流。 但是事實證明,有兩種方法可以直接從數組轉換為流。 這是第一種方法。 我將不需要這三行代碼來處理數據。 因此,我將其注釋掉。 然后在這里,我將聲明一個對象,其類型為流。
Stream是接口,它是java.util.stream的成員。 當我按Ctrl + Space并從列表中選擇它時,系統會詢問我該流將管理的項目的通用類型。 這些將是Person類型的項目,就像數組本身中的項目一樣。 我將用小寫字母命名新的流對象stream。 這是創建流的第一種方法。 再次使用流接口,并調用名為of()的方法。 請注意,有幾個不同的版本。
一個帶有單個對象,另一個帶有一系列對象。 我將使用帶有一個參數的參數,然后將其傳遞給數組people ,這就是我需要做的所有事情。 Stream.of()表示接受此數組并將其包裝在流中。 現在,我可以使用lambda表達式,過濾器,方法引用和對Stream對象起作用的其他東西。 我將為每個方法調用流對象,我將傳遞一個lambda表達式,我將傳遞當前人,然后在lambda運算符之后,我將輸出該人的信息。 使用對象的getInfo()方法。
Person[] people = {new Person("Mohamed", 69),new Person("Doaa", 25),new Person("Malik", 6)};// for (int i = 0; i < people.length; i++) { // System.out.println(people[i].getInfo()); // }Stream<Person> stream = Stream.of(people);stream.forEach(p -> System.out.println(p.getInfo()));我將保存并運行代碼,并得到結果。 我以將它們放在數組中的相同順序輸出項目。 因此,這是使用Stream.of()的一種方法。
Mohamed (69) Doaa (25) Malik (6)還有另一種方法可以做完全相同的事情。 我將復制該行代碼,并注釋掉一個版本。 這次使用Stream.of() ,我將使用一個名為Arrays的類,該類是包java.util的成員。
從那里,我將調用一個名為stream的方法。 注意,stream方法可以包裝在各種類型的數組周圍。 包括基本體和復雜對象。
// Stream<person> stream = Stream.of(people);Stream<person> stream = Arrays.stream(people);stream.forEach(p -> System.out.println(p.getInfo()));我將保存并運行該版本,流將執行與以前完全相同的操作。
Mohamed (69) Doaa (25) Malik (6)結論
因此, Stream.of()或Arrays.stream()會做完全相同的事情。 取一個原始值或復雜對象的數組,然后將它們轉換為流,然后可以將其與lambda,過濾器和方法引用一起使用。
3.匯總流值
介紹
前面已經描述了如何使用流來迭代集合。 但是,您也可以使用流來聚合集合中的項目。 也就是說,計算求和 , 平均值 , 計數等。 當您執行此類操作時,了解并行流的本質很重要。
怎么運行的
所以我要在項目啟動這個示范Java8Features ,在包eg.com.tm.java8.features.stream.aggregating 。 我將首先使用ParallelStreams類。 在此類的main方法中,我創建了一個包含字符串項目的數組列表。
我正在使用一個簡單的for循環,已將10,000個項目添加到列表中。 然后在第35和36行上,我創建一個流,并為每種方法使用,并一次輸出一個流。
public static void main(String args[]) {System.out.println("Creating list");List<string> strings = new ArrayList<>();for (int i = 0; i < 10000; i++) {strings.add("Item " + i);}strings.stream().forEach(str -> System.out.println(str)); }當我運行此代碼時,我得到了預期的結果。 這些項目按照添加到列表的順序輸出到屏幕。
......... Item 9982 Item 9983 Item 9984 Item 9985 Item 9986 Item 9987 Item 9988 Item 9989 Item 9990 Item 9991 Item 9992 Item 9993 Item 9994 Item 9995 Item 9996 Item 9997 Item 9998 Item 9999現在,讓我們看看將其轉換為并行流時會發生什么。 如前所述,我可以通過調用并行流方法或通過獲取流的結果并將其傳遞給并行來實現。
我會做后者。 現在,我正在使用并行流,該流可以分解并且工作負載可以在多個處理器之間分配。
strings.stream().parallel().forEach(str -> System.out.println(str));我將再次運行代碼并觀察會發生什么,請注意,打印的最后一項不是列表中的最后一項。 那將是9,999。 而且,如果我在輸出中滾動,我會看到處理以某種方式跳躍。 發生的事情是運行時將數據任意拆分為多個塊。
......... Item 5292 Item 5293 Item 5294 Item 5295 Item 5296 Item 5297 Item 5298 Item 5299 Item 5300 Item 5301 Item 5302 Item 5303 Item 5304 Item 5305 Item 5306 Item 5307 Item 5308 Item 5309 Item 5310 Item 5311然后將每個塊交給可用的處理器。 只有在處理完所有塊之后,才會執行我的下一部分Java代碼。 但是在內部,在對forEach()方法的調用中,所有這些工作都根據需要進行了拆分。 現在,這可能會或可能不會帶來性能上的好處。 這取決于數據集的大小。 以及您的硬件的性質。 但是,此示例向您展示的一件事是,如果需要順序處理項目,即一次將它們添加到集合中的順序相同,那么并行流可能不是這樣做的方法。它。
順序流可以確保每次都以相同的順序工作。 但是,按照定義,并行流將以最有效的方式處理事務。 因此,并行流在聚合操作時特別有用。 您要考慮集合中的所有項目,然后從中創建某種總價值。 我將向您展示一些示例,這些示例包括對集合中的項目進行計數,取平均值并使用字符串對其求和。
在此類main方法中的CountItems中,我從相同的基本代碼開始。 在列表中創建10,000個字符串。 然后,每種方法都有一個循環遍歷并一次處理它們的方法。
public static void main(String args[]) {System.out.println("Creating list");List<string> strings = new ArrayList<>();for (int i = 0; i < 10000; i++) {strings.add("Item " + i);}strings.stream().forEach(str -> System.out.println(str)); }在此示例中,我不是要單獨處理每個字符串,而是要對它們進行計數。 因此,我將注釋掉該代碼,這是我將使用的代碼。 由于我不知道確切要收集多少物品。 我將兌現要創建的結果作為一個長整數。
我將其命名為count ,并通過調用strings獲得它的值。 那是我的集合.stream() .count() ,并且返回一個長值。 然后,我將使用系統輸出并報告結果。 用count:然后附加結果。
// strings.stream() // .forEach(str -> System.out.println(str));long count = strings.stream().count();System.out.println("Count: " + count);我將保存更改并運行代碼,并得到結果。 集合中項目的計數幾乎是瞬時的。
Creating list Count: 10000現在,為了使它更具戲劇性,我將在此處添加幾個零,現在我要處理1,000,000,000個字符串。 我將再次運行代碼,結果幾乎立即又返回。
Creating list Count: 1000000現在看看如果我并行化字符串會發生什么。 我將在此處平行添加點:
// strings.stream() // .forEach(str -> System.out.println(str));long count = strings.stream().parallel().count();System.out.println("Count: " + count);然后我將運行代碼,這將花費更長的時間。 現在,我可以通過捕獲操作前后的當前時間戳來確定執行這些操作需要多長時間。 然后做一點數學。 它將顯示的內容可能因一個系統而異。 但是以我的經驗,當處理包含簡單值的這類簡單集合時,并行流并沒有太大的好處。 您的里程可能會很高。 我鼓勵您做自己的基準測試。 但這就是您要計數的方式。
讓我們看一下求和和求平均值 。 我去上課SumAndAverage 。 這次,我列出了三個人對象,每個對象的年齡不同。 我的目標是獲得三個年齡的總和,以及三個年齡的平均值。 在person類的所有實例都添加到列表之后,我將添加一行新代碼。 然后創建一個整數變量,命名為sum 。
我將從使用people.stream().獲得流開始people.stream(). 從那里,我將調用一個名為mapToInt()的方法。 注意,這里有一個Map方法。 mapToDouble()和mapToLong()也是如此。 這些方法的目的是獲取復雜的對象并從中提取簡單的原始值,并創建這些值的流,然后使用Lambda表達式執行此操作。 因此,我將選擇mapToInt()因為每個人的年齡都是整數。
對于Lambda表達式,我將從代表當前人的變量開始。 然后是Lambda運算符,然后是一個返回整數的表達式。 我將使用p.getAge() 。 這將返回稱為int字符串或整數字符串的內容。 還有一個雙字符串類和其他一些類。 現在從這個流中開始,因為我已經知道它是一個數值,所以我可以調用一個名為sum()的方法。 就是這樣。 現在,我總結了我收藏中所有交友對象的所有陳舊價值。 僅需一條語句,我將使用系統輸出輸出結果。 我的標簽將是年齡的總和,我將在其后加上總和。
List<person> people = new ArrayList<>();people.add(new Person("Mohamed", 69));people.add(new Person("Doaa", 25));people.add(new Person("Malik", 6));int sum = people.stream().mapToInt(p -> p.getAge()).sum();System.out.println("Total of ages " + sum);我將保存我的代碼并運行它。 這三個年齡段的總和為100。
Total of ages 100平均這些值非常相似。 但是,因為每當進行平均除法運算時,都可能會遇到被零除的問題,因此,當您進行平均時,您會得到稱為Optional變量的信息。
您可以使用多種類型。 對于我的平均而言,我期望一個雙精度值會回來。 因此,我將創建一個名為OptionalDouble的變量。 注意,還有可選的Int和可選的日志。 我將Avg命名為變量Avg 。 我將使用剛才用來獲取總和的相同類型的代碼,從people.stream() 。 然后從那里,我將再次使用mapToInt() 。 然后,我將傳遞上次使用的相同的lambda表達式,然后從那里調用average方法。
現在有了OptionalDouble對象,在處理它之前,應始終確保它實際上具有一個double值,并使用名為isPresent()的方法來執行此操作。 因此,我將從一個if else代碼模板開始。 然后將條件設置為avg.isPresent() 。 如果條件成立,我將使用系統輸出。 我將其標記為“平均”。 然后我將附加平均變量。 在else子句中,我將簡單地說平均數未計算出來。
OptionalDouble avg = people.stream().mapToInt(p -> p.getAge()).average(); if (avg.isPresent()) {System.out.println("Average: " + avg); } else {System.out.println("average wasn't calculated"); }現在,在這個示例中,我知道它將成功,因為我已經為所有三個人提供了年齡,但情況并非總是如此。 就像我說的那樣,如果最終遇到被零除的情況,則可能無法獲得雙倍的價值。 我將保存并運行代碼,并注意使用可選的double類,它是一個復雜的對象。
Total of ages 100 Average: OptionalDouble[33.333333333333336]因此,類型將包裹在實際值周圍。 我將轉到此代碼,直接在其中引用該對象,并將其getAsDouble()方法。
if (avg.isPresent()) {System.out.println("Average: " + avg.getAsDouble()); } else {System.out.println("average wasn't calculated"); }現在,我將返回原始的double值。 我將再次運行代碼,現在結果就是我想要的。
Total of ages 100 Average: 33.333333333333336結論
因此,使用流和lambda表達式,您可以使用少量代碼輕松地從集合中計算聚合值。
資源資源
我希望您喜歡閱讀它,就像我喜歡編寫它一樣,如果您喜歡它,請分享,傳播信息。
翻譯自: https://www.javacodegeeks.com/2015/07/java-se-8-new-features-tour-processing-collections-with-streams-api.html
總結
以上是生活随笔為你收集整理的Java SE 8新功能介绍:使用Streams API处理集合的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java递归基础掉用_Java递归基础
- 下一篇: 歌词刮风这天 歌词刮风这天的完整歌词