自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java 8新特性之旅:使用Stream API處理集合

開發(fā) 后端
在這篇“Java 8新特性教程”系列文章中,我們會深入解釋,并通過代碼來展示,如何通過流來遍歷集合,如何從集合和數(shù)組來創(chuàng)建流,以及怎么聚合流的值。

在這篇“Java 8新特性教程”系列文章中,我們會深入解釋,并通過代碼來展示,如何通過流來遍歷集合,如何從集合和數(shù)組來創(chuàng)建流,以及怎么聚合流的值。

在之前的文章“遍歷、過濾、處理集合及使用Lambda表達式增強方法”中,我已經(jīng)深入解釋并演示了通過lambda表達式和方法引用來遍歷集合,使用predicate接口來過濾集合,實現(xiàn)接口的默認方法,最后還演示了接口靜態(tài)方法的實現(xiàn)。

源代碼都在我的Github上:可以從 這里克隆。

內(nèi)容列表

  • 使用流來遍歷集合。

  • 從集合或數(shù)組創(chuàng)建流。

  • 聚合流中的值。

1. 使用流來遍歷集合

簡介:

Java的集合框架,如List和Map接口及Arraylist和HashMap類,讓我們很容易地管理有序和無序集合。集合框架自引入的第一天 起就在 持續(xù)的改進。在Java SE 8中,我們可以通過流的API來管理、遍歷和聚合集合。一個基于流的集合與輸入輸出流是不同的。

如何工作?

它采用一種全新的方式,將數(shù)據(jù)作為一個整體,而不是單獨的個體來處理。當你使用流時,你不需要關(guān)心循環(huán)或遍歷的細節(jié)。你可以直接從一個集合創(chuàng)建一個 流。然 后你就能用這個流來許多事件了,如遍歷、過濾及聚和。我將從項目 Java8Features 的 com.tm.java8.features.stream.traversing 包下的例子開始。代碼在一個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());
         }
     });
}

在這兩種流中,串行流相對比較簡單,它類似一個迭代器,每次處理集合中的一個元素。但是語法與以前不同。在這段代碼中,我創(chuàng)建了 pepole 的數(shù)組列表,向上轉(zhuǎn)型為List。它包含三個 Person 類的實例。然后我們使用 Predicate 聲明一個條件,只有滿足這個條件的 people 才會顯示。在 displayPeople() 方法的48到52行循環(huán)遍歷該集合,挨個測試其中的每一項。運行這段代碼,你將獲得如下的結(jié)果:

Selected:
Mohamed

我將會展示如何使用流來重構(gòu)這段代碼。首先,我注釋了這段代碼。然后,在這段注釋的代碼下,我開始使用集合對象 people。然后我調(diào)用一個 stream() 方法。一個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()));
}

你可以調(diào)用一個 stream() 方法來獲得了一個流對象,然后可以在該對象上進行一些操作。我簡單地調(diào)用了 forEach 方法,該方法需要一個Lamda表達式。我在參數(shù)中傳遞了一個Lamda表達式。列表中的每一項就是通過迭代器處理的每一項。處理過程是通過Lambda 操作符和方法實現(xiàn)來完成的。我簡單使用system output來輸出每個人的名稱。保存并運行這段代碼,輸出結(jié)果如下。因為沒有過濾,所以輸出了列表中所有元素。

Selected:
Mohamed
Doaa
Malik

現(xiàn)在,一旦有了一個流對象,就可以很容易使用 predicate 對象了。當使用 for each 方法處理每一項時,我不得不顯示調(diào)用 predicate 的 test 方法,但是使用流時,你可以調(diào)用一個名為 filter 的方法。該方法接收一個 predicate 對象,所有的 predicate 對象都有一個 test 方法,所以它已經(jīng)知道怎樣去調(diào)用該方法。所以,我對該代碼做一點改動。我將.forEach()方法下移了兩行,然后在中間的空白行,我調(diào)用了 filter 方法。

people.stream()
     .filter(pred)
     .forEach(p -> System.out.println(p.getName()));

filter方法接收一個 predicate 接口的實例對象。我將 predicate 對象傳進去。filtr 方法返回一個過濾后的流對象,在這個對象上我就可以去調(diào)用forEach()方法了。我運行這段代碼,這次我只顯示集合中滿足預(yù)定義條件的項了。你可以在 流對象上做更多的事情。去看看 Java SE 8 API 中流的doc文檔吧。

Selected:
Mohamed

你將會看到除了過濾,你還可以做聚合、排序等其他的事情。在我總結(jié)這段演示之前,我想向你們展示一下串行流和并行流之前的重要區(qū)別。Java SE 8 的一個重要目標就是改善多 CPU 系統(tǒng)的處理能力。Java 可在運行期自動協(xié)調(diào)多個 CPU 的運行。你需要做的所有事情僅僅是將串行流轉(zhuǎn)換為并行流。

從語法上講,有兩種方法來實現(xiàn)流的轉(zhuǎn)換。我復(fù)制一份串行流類。在包視圖窗口,我復(fù)制并粘貼該類,然后對它重命名,ParallelStream,打 開這個 新的類。在這個版本中,刪除了注釋的代碼。我不再需要這些注釋了?,F(xiàn)在就可以通過兩種方式創(chuàng)建并行流。第一種方式是調(diào)用集合中的 parallelStream()方法?,F(xiàn)在我就擁有一個可以自動分配處理器的流了。

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()));
}

運行這段代碼,就可以看到完全一致的結(jié)果,過濾然后返回數(shù)據(jù)。

Selected:
Mohamed

第二種創(chuàng)建并行流的方式。再次調(diào)用 stream() 方法,然后在 stream 方法的基礎(chǔ)上調(diào)用 parallel() 方法,其本質(zhì)上做的事情是一樣的。開始是一個串行的流,然后再將其轉(zhuǎn)換為并行流。但是它仍然是一個流??梢赃^濾,可以用之前的一樣方式去處理。只是現(xiàn)在的 流可以分解到多個處理起來處理。

people.stream()
      .parallel()
      .filter(pred)
      .forEach(p -> System.out.println(p.getName()));

總結(jié)

現(xiàn)在還沒有一個明確的規(guī)定來說明在什么情況下并行流優(yōu)于串行流。這個依賴于數(shù)據(jù)的大小和復(fù)雜性以及硬件的處理能力。還有你運行的多 CPU 系統(tǒng)。我可以給你的唯一建議是測試你的應(yīng)用和數(shù)據(jù)。建立一個基準的、計時的操作。然后分別使用串行流和并行流,看哪一個更適合于你。

2、從集合或數(shù)組創(chuàng)建流

簡介

Java SE 8’s stream API 是為了幫助管理數(shù)據(jù)集合而設(shè)計的,這些對象是指集合框架中的對象,例如數(shù)組列表或哈希表。但是,你也可以直接從數(shù)組創(chuàng)建流。

如何工作?

在 Java8Features 項目中的 eg.com.tm.java8.features.stream.creating 包下,我創(chuàng)建了一個名為ArrayToStream的類。在這個類的 main 方法中,我創(chuàng)建了一個包含三個元素的數(shù)組。每個元素都是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());
    }
}

該類中為私有成員創(chuàng)建了 setters 和 getters 方法,以及 getInfo() 方法,該方法返回一個拼接的字符串。

public String getInfo() {
    return name + " (" + age + ")";
}

現(xiàn)在,如果想使用流來處理這個數(shù)組,你可能認為需要先將數(shù)組轉(zhuǎn)為數(shù)組列表,然后從這個列表創(chuàng)建流。但是,實際上你可以有兩種方式直接從數(shù)組創(chuàng)建流。第一方式,我不需要處理數(shù)據(jù)的那三行代碼,所以先注釋掉。然后,在這個下面,我聲明一個流類型的對象。

Stream 是 java.util.stream 下的一個接口。當我按下 Ctrl+Space 并選取它的時候,會提示元素的泛型,這就是流管理的類型。在這里,元素的類型即為Person,與數(shù)組元素本身的類型是一致的。我將我新的流對象命名為 stream,所有的字母都是小寫的。這就是第一種創(chuàng)建流的方法,使用流的接口,調(diào)用 of() 方法。注意,該方法存在兩個不同版本。

第一個是需要單個對象,第二個是需要多個對象。我使用一個參數(shù)的方法,所以傳遞一個名為 people 的數(shù)組,這就是我需要做的所有事情。Stream.of() 意思就是傳入一個數(shù)組,然后將該數(shù)組包裝在流中?,F(xiàn)在,我就可以使用 lambda 表達式、過濾、方法引用等流對象的方法。我將調(diào)用流的 for each 方法,并傳入一個 lambda 表達式,將當前的 person 對象和 lambda 操作符后傳入后,就能獲取到 person 對象的信息。該信息是通過對象的 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()));

保存并運行這段代碼,就可獲取到結(jié)果。輸出的元素的順序與我放入的順序是一致的。這就是第一種方式:使用 Stream.of() 方法。

Mohamed (69)
Doaa (25)
Malik (6)

另一種方式與上面的方式實際上是相同的。復(fù)制上面的代碼,并注釋掉第一種方式。這次不使用 Stream.of() 方法,我們使用名為 Arrays 的類,該類位于 java.util 包下。在這個類上,可以調(diào)用名為 stream 的方法。注意,stream 方法可以包裝各種類型的數(shù)組,包括基本類型和復(fù)合類型。

//      Stream<person> stream = Stream.of(people);

        Stream<person> stream = Arrays.stream(people);
        stream.forEach(p -> System.out.println(p.getInfo()));

保存并運行上面的代碼,流完成的事情與之前實質(zhì)上是一致的。

Mohamed (69)
Doaa (25)
Malik (6)

結(jié)論

所以,無論是 Stream.of() 還是 Arrays.stream(),所做的事情實質(zhì)上是一樣的。都是從一個基本類型或者復(fù)合對象類型的數(shù)組轉(zhuǎn)換為流對象,然后就可以使用 lambda 表達式、過濾、方法引用等功能了。

3、聚合流的值

簡介

之前,我已經(jīng)描述過怎么使用一個流來迭代一個集合。你也可以使用流來聚合集合中的每一項。如計算總和、平均值、總數(shù)等等。當你做這些操作的時候,弄明白并行流特性就非常重要。

如何工作?

我會在 Java8Features 項目的 eg.com.tm.java8.features.stream.aggregating 包下進行演示。首先我們使用 ParallelStreams 類。在這個類的 main 方法中,我創(chuàng)建了一個包含字符串元素的數(shù)組列表。我簡單地使用循環(huán)在列表中添加了10000個元素。然后在35和36行,我創(chuàng)建了一個流對象,并通過 for each 方法挨個輸出流中每一項。

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));
}

運行這段代碼后,就獲得了一個我所預(yù)期的結(jié)果。在屏幕上輸出的順序與添加到列表中的順序是一致的。

.........
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

現(xiàn)在,讓我們看一下當轉(zhuǎn)換成并行流后會發(fā)生什么。正如我之前所描述的,我即可以調(diào)用parallelStream方法,也可以在流上調(diào)用parallel方法。

我將采用第二種方法?,F(xiàn)在,我就可以使用并行流了,該流可以根據(jù)負載分配到多個處理器來處理。

strings.stream()
       .parallel()
       .forEach(str -> System.out.println(str));

再次運行該段代碼,然后觀察會發(fā)生什么。注意,現(xiàn)在最后打印的元素不是列表中最后一個元素,最后一個元素應(yīng)該是9999。如果我滾動輸出結(jié)果,就能發(fā)現(xiàn)處理過程以某種方式在循環(huán)跳動。這是因為在運行時將數(shù)據(jù)劃分成了多個塊。

.........
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

然后,將數(shù)據(jù)塊分配給合適的處理器去處理。只有當所有塊都處理完成了,才會執(zhí)行之后的代碼。本質(zhì)上講,這是在調(diào)用 forEach() 方法時,將整個過程是根據(jù)需要來進行劃分了?,F(xiàn)在,這么做可能會提高性能,也可能不會。這依賴于數(shù)據(jù)集的大小以及你硬件的性能。通過這個例子,也可以看 出,如果需要按照添加的順序挨個處理每一項,那么并行流可能就不合適了。

串行流能保證每次運行的順序是一致的。但并行流,從定義上講,是一種更有效率的方式。所以并行流在聚合操作的時候非常有效。很適合將集合作為一個整體考慮,然后在該集合上進行一些聚合操作的情況。我將會通過一個例子來演示集合元素的計數(shù)、求平均值及求和操作。

我們在這個類的 main 方法中來計數(shù),開始還是用相同的基礎(chǔ)代碼。創(chuàng)建10,000個字符串的列表。然后通過一個 for each 方法循環(huán)處理每一項。

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));
}

在這個例子中,我想直接對集合元素進行計數(shù),而不是挨個來處理。所以,我注釋掉原來的代碼,使用下面的代碼。因為不能準確的知道該集合到底有多少個元素。所以我使用長整型變量來存儲結(jié)果。

我將這個變量命名為count,通過調(diào)用集合strings的.stream(), .count()方法,返回一個長整型的值。然后將這個值與“count:”拼接起來,再通過system的output來打印。

//      strings.stream()
//             .forEach(str -> System.out.println(str));
        long count = strings.stream().count();
        System.out.println("Count: " + count);

保存并運行該段代碼,下面是輸出結(jié)果。集合中元素數(shù)量的統(tǒng)計幾乎是瞬間完成。

Creating list
Count: 10000

現(xiàn)在對上面的代碼做一點小小的改動,增加兩個0?,F(xiàn)在,開始處理1000,000個字符串。我再次運行這段代碼,也很快就返回結(jié)果了。

Creating list
Count: 1000000

現(xiàn)在,我使用并行流來處理,看會發(fā)生什么。我在下面增加 parallel 方法:

//      strings.stream()
//             .forEach(str -> System.out.println(str));
        long count = strings.stream().parallel().count();
        System.out.println("Count: " + count);

然后我運行這段代碼,發(fā)現(xiàn)花費的時間更長一點了?,F(xiàn)在,我做一個基準測試,通過抓取操作前后的時間戳來觀察發(fā)生了什么。然后做一點數(shù)學的事情。不同 的系統(tǒng) 上,得到的結(jié)果可能不同。但是根據(jù)我的經(jīng)驗來說,這種包含簡單類型的簡單集合,使用并行流并沒有太多的優(yōu)勢。不過,我還是鼓勵你去自己做基準測試,雖然有 點麻煩。 不過這也要你是如何去做的。

再讓我們看一下求和及求均值。我將使用 SumAndAverage 類。這次,我有一個包含三個 person 對象的列表,每個 person 對象的有不同的年齡值。我的目的是求三個年齡的和及年齡的平均值。我在所有的 person 對象都加入到列表之后加入了一行新的代碼。然后,我創(chuàng)建了一個名為sum的整型變量。

首先,我通過 pepole.stream() 方法獲取一個流。在這個流基礎(chǔ)上,我可以調(diào)用 mapToInt() 方法。注意,還有兩個類似的 Map Method:mapToDouble() 和 mapToLong()。這些方法的目的就是,從復(fù)合類型中獲取簡單的基本類型數(shù)據(jù),創(chuàng)建流對象。你可以用 lambda 表達式來完成這項工作。所以,我選擇 mapToInt() 方法,因為每個人的年齡都是整數(shù)。

關(guān)于 Lambda 表達式,開始是一個代表當前 person 的變量。然后,通過 Lambda 操作符和 Lambda 表達式(p.getAge())返回一個整數(shù)。這種返回值,我們有時也叫做int字符串。也可以返回double字符串或其它類型?,F(xiàn)在,由于已經(jīng)知道它 是一個數(shù)字類型的值,所以我可以調(diào)用 sum() 方法。現(xiàn)在,我就已經(jīng)將所有集合中 person 對象的年齡值全部加起來了。通過一條語句,我就可以用 System Output 來輸出結(jié)果了。我將求和的結(jié)果與“Total of ages”連接在一起輸出。

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

求這些值的平均值非常類似。但是,求平均值需要做除法操作,所以需要考慮除數(shù)為0的問題,因此,當你求平均值的時候,可以返回一個Optional的變量。

你可以使用多種數(shù)據(jù)類型。在計算平均值的時候,我想獲得一個 doule 類型的值。所以,我創(chuàng)建了一個 OptionalDouble 類型的變量。注意,還存在 Optional Int 和 Optional Long。我將平均值命名為 avg,使用的代碼與求和的代碼也是一致的,開始用 people.stream()。在這個基礎(chǔ)上,再次使用 mapToInt()。并且傳遞了相同的 lambda 表達式,最后,調(diào)用 average 方法。

現(xiàn)在,獲得了一個OptionalDouble類型的變量。在處理這個變量前,你可以通過 isPresent() 來確保它確實是一個double值。所以,我使用了一段 if/else 的模板代碼來處理。判定的條件是 avg.isPresent()。如果條件為真,就使用 System Output 輸出“Average”標簽和平均值。在 else 子句中,我簡單地打印“average wasn’t calculated”。

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");
}

現(xiàn)在,在這個例子中,我知道能成功,因為我給三個人的年齡都賦值了。但是,情況不總是這樣的。正如我前面說的,存在除0的情況,這時你就不能獲取到一個 double 類型返回值。我保存并運行這段代碼,請注意 optional double 類,它是一個復(fù)合對象。

Total of ages 100
Average: OptionalDouble[33.333333333333336]

所以,真實的值被包含在該類型中,回到這段代碼,直接引用該對象,并調(diào)用 getAsDouble() 方法。

if (avg.isPresent()) {
    System.out.println("Average: " + avg.getAsDouble());
} else {
    System.out.println("average wasn't calculated");
}

現(xiàn)在,我就可以獲得 double 類型的值。我再次運行這段代碼,輸出結(jié)果如下:

Total of ages 100
Average: 33.333333333333336

結(jié)論

通過流和 lambda 表達式,你可以用非常非常少的代碼就可以完成集合的聚合計算。

責任編輯:王雪燕 來源: ImportNew
相關(guān)推薦

2019-06-27 10:32:57

Java開發(fā)代碼

2019-03-11 09:18:20

Java 8Stream數(shù)據(jù)結(jié)構(gòu)

2023-05-12 07:40:01

Java8API工具

2021-02-22 11:51:15

Java開發(fā)代碼

2024-10-09 08:42:03

2014-07-15 14:48:26

Java8

2022-12-09 07:48:10

Java8Stream表達式

2022-12-30 09:24:23

Java8Stream操作

2021-04-12 07:34:03

Java集合框架

2014-10-20 13:57:59

JavaFX 8Java 8

2024-04-19 08:28:57

JavaAPI場景

2021-05-06 20:03:00

JavaStream代碼

2014-07-14 11:34:53

Java 8Nashorn

2024-02-04 08:35:03

APIJava 8數(shù)據(jù)庫

2024-02-02 11:18:37

Java 8API對象

2014-03-19 11:04:14

Java 8Java8特性

2014-04-15 15:45:22

Java8Java8教程

2014-05-05 09:58:01

2013-05-02 09:14:19

Java 8Java 8的新特性

2014-04-16 07:43:31

Java 8JRE
點贊
收藏

51CTO技術(shù)棧公眾號