上節(jié)我們介紹了Lambda表達(dá)式和函數(shù)式接口,本節(jié)探討它們的應(yīng)用,函數(shù)式數(shù)據(jù)處理,針對(duì)常見的集合數(shù)據(jù)處理,Java 8引入了一套新的類庫(kù),位于包java.util.stream下,稱之為Stream API,這套API操作數(shù)據(jù)的思路,不同于我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">38節(jié)55節(jié)介紹的容器類API,它們是函數(shù)式的,非常簡(jiǎn)潔、靈活、易讀,具體有什么不同呢?由于內(nèi)容較多,我們分為兩節(jié)來介紹,本節(jié)先介紹一些基本的API,下節(jié)討論一些高級(jí)功能。

基本概念

接口Stream類似于一個(gè)迭代器,但提供了更為豐富的操作,Stream API的主要操作就定義在該接口中。 Java 8給Collection接口增加了兩個(gè)默認(rèn)方法,它們可以返回一個(gè)Stream,如下所示:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

default Stream<E> stream() {    return StreamSupport.stream(spliterator(), false);
}default Stream<E> parallelStream() {    return StreamSupport.stream(spliterator(), true);
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

stream()返回的是一個(gè)順序流,parallelStream()返回的是一個(gè)并行流。順序流就是由一個(gè)線程執(zhí)行操作。而并行流背后可能有多個(gè)線程并行執(zhí)行,與之前介紹的并發(fā)技術(shù)不同,使用并行流不需要顯式管理線程,使用方法與順序流是一樣的。

下面,我們主要針對(duì)順序流,學(xué)習(xí)Stream接口,包括其用法和基本原理,隨后我們?cè)俳榻B下并行流。先來看一些簡(jiǎn)單的示例。

基本示例

上節(jié)演示時(shí)使用了學(xué)生類Student和學(xué)生列表List<Student> lists,本節(jié)繼續(xù)使用它們。

基本過濾

返回學(xué)生列表中90分以上的,傳統(tǒng)上的代碼一般是這樣的:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<Student> above90List = new ArrayList<>();for (Student t : students) {    if (t.getScore() > 90) {
        above90List.add(t);
    }
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

使用Stream API,代碼可以這樣:

List<Student> above90List = students.stream()
        .filter(t->t.getScore()>90)
        .collect(Collectors.toList());

先通過stream()得到一個(gè)Stream對(duì)象,然后調(diào)用Stream上的方法,filter()過濾得到90分以上的,它的返回值依然是一個(gè)Stream,為了轉(zhuǎn)換為L(zhǎng)ist,調(diào)用了collect方法并傳遞了一個(gè)Collectors.toList(),表示將結(jié)果收集到一個(gè)List中。

代碼更為簡(jiǎn)潔易讀了,這種數(shù)據(jù)處理方式被稱為函數(shù)式數(shù)據(jù)處理,與傳統(tǒng)代碼相比,它的特點(diǎn)是:

  • 沒有顯式的循環(huán)迭代,循環(huán)過程被Stream的方法隱藏了

  • 提供了聲明式的處理函數(shù),比如filter,它封裝了數(shù)據(jù)過濾的功能,而傳統(tǒng)代碼是命令式的,需要一步步的操作指令

  • 流暢式接口,方法調(diào)用鏈接在一起,清晰易讀

基本轉(zhuǎn)換

根據(jù)學(xué)生列表返回名稱列表,傳統(tǒng)上的代碼一般是這樣:

List<String> nameList = new ArrayList<>(students.size());for (Student t : students) {
    nameList.add(t.getName());
}

使用Stream API,代碼可以這樣:

List<String> nameList = students.stream()
        .map(Student::getName)
        .collect(Collectors.toList());

這里使用了Stream的map函數(shù),它的參數(shù)是一個(gè)Function函數(shù)式接口,這里傳遞了方法引用。       

基本的過濾和轉(zhuǎn)換組合

返回90分以上的學(xué)生名稱列表,傳統(tǒng)上的代碼一般是這樣:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<String> nameList = new ArrayList<>();for (Student t : students) {    if (t.getScore() > 90) {
        nameList.add(t.getName());
    }
}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

使用函數(shù)式數(shù)據(jù)處理的思路,可以將這個(gè)問題分解為由兩個(gè)基本函數(shù)實(shí)現(xiàn):

  1. 過濾:得到90分以上的學(xué)生列表

  2. 轉(zhuǎn)換:將學(xué)生列表轉(zhuǎn)換為名稱列表

使用Stream API,可以將基本函數(shù)filter()和map()結(jié)合起來,代碼可以這樣:

List<String> above90Names = students.stream()
        .filter(t->t.getScore()>90)
        .map(Student::getName)
        .collect(Collectors.toList());

這種組合利用基本函數(shù)、聲明式實(shí)現(xiàn)集合數(shù)據(jù)處理功能的編程風(fēng)格,就是函數(shù)式數(shù)據(jù)處理。

代碼更為直觀易讀了,但你可能會(huì)擔(dān)心它的性能有問題。filter()和map()都需要對(duì)流中的每個(gè)元素操作一次,一起使用會(huì)不會(huì)就需要遍歷兩次呢?答案是否定的,只需要一次。實(shí)際上,調(diào)用filter()和map()都不會(huì)執(zhí)行任何實(shí)際的操作,它們只是在構(gòu)建操作的流水線,調(diào)用collect才會(huì)觸發(fā)實(shí)際的遍歷執(zhí)行,在一次遍歷中完成過濾、轉(zhuǎn)換以及收集結(jié)果的任務(wù)。

像filter和map這種不實(shí)際觸發(fā)執(zhí)行、用于構(gòu)建流水線、返回Stream的操作被稱為中間操作(intermediate operation),而像collect這種觸發(fā)實(shí)際執(zhí)行、返回具體結(jié)果的操作被稱為終端操作(terminal operation)。Stream API中還有更多的中間和終端操作,下面我們具體來看下。

中間操作

除了filter和map,Stream API的中間操作還有distinct, sorted, skip, limit, peek, mapToLong, mapToInt, mapToDouble, flatMap等,我們逐個(gè)來看下。

distinct

distinct返回一個(gè)新的Stream,過濾重復(fù)的元素,只留下唯一的元素,是否重復(fù)是根據(jù)equals方法來比較的,distinct可以與其他函數(shù)如filter, map結(jié)合使用。

比如,返回字符串列表中長(zhǎng)度小于3的字符串、轉(zhuǎn)換為小寫、只保留唯一的,代碼可以為:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<String> list = Arrays.asList(new String[]{"abc","def","hello","Abc"});
List<String> retList = list.stream()
        .filter(s->s.length()<=3)
        .map(String::toLowerCase)
        .distinct()
        .collect(Collectors.toList());
System.out.println(retList);

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

輸出為:

[abc, def]

雖然都是中間操作,但distinct與filter和map是不同的,filter和map都是無狀態(tài)的,對(duì)于流中的每一個(gè)元素,它的處理都是獨(dú)立的,處理后即交給流水線中的下一個(gè)操作,但distinct不同,它是有狀態(tài)的,在處理過程中,它需要在內(nèi)部記錄之前出現(xiàn)過的元素,如果已經(jīng)出現(xiàn)過,即重復(fù)元素,它就會(huì)過濾掉,不傳遞給流水線中的下一個(gè)操作。

對(duì)于順序流,內(nèi)部實(shí)現(xiàn)時(shí),distinct操作會(huì)使用HashSet記錄出現(xiàn)過的元素,如果流是有順序的,需要保留順序,會(huì)使用LinkedHashSet。

sorted

有兩個(gè)sorted方法:

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

它們都對(duì)流中的元素排序,都返回一個(gè)排序后的Stream,第一個(gè)方法假定元素實(shí)現(xiàn)了Comparable接口,第二個(gè)方法接受一個(gè)自定義的Comparator。

比如,過濾得到90分以上的學(xué)生,然后按分?jǐn)?shù)從高到低排序,分?jǐn)?shù)一樣的,按名稱排序,代碼可以為:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<Student> list = students.stream()
        .filter(t->t.getScore()>90)
        .sorted(Comparator.comparing(Student::getScore)
                .reversed()
                .thenComparing(Student::getName))
        .collect(Collectors.toList());

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

這里,使用了Comparator的comparing, reversed和thenComparing構(gòu)建了Comparator。

與distinct一樣,sorted也是一個(gè)有狀態(tài)的中間操作,在處理過程中,需要在內(nèi)部記錄出現(xiàn)過的元素,與distinct不同的是,每碰到流中的一個(gè)元素,distinct都能立即做出處理,要么過濾,要么馬上傳遞給下一個(gè)操作,但sorted不能,它需要先排序,為了排序,它需要先在內(nèi)部數(shù)組中保存碰到的每一個(gè)元素,到流結(jié)尾時(shí),再對(duì)數(shù)組排序,然后再將排序后的元素逐個(gè)傳遞給流水線中的下一個(gè)操作。

skip/limit

它們的定義為:

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

skip跳過流中的n個(gè)元素,如果流中元素不足n個(gè),返回一個(gè)空流,limit限制流的長(zhǎng)度為maxSize。

比如,將學(xué)生列表按照分?jǐn)?shù)排序,返回第3名到第5名,代碼可以為:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<Student> list = students.stream()
        .sorted(Comparator.comparing(
            Student::getScore).reversed())
        .skip(2)
        .limit(3)
        .collect(Collectors.toList());

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

skip和limit都是有狀態(tài)的中間操作。對(duì)前n個(gè)元素,skip的操作就是過濾,對(duì)后面的元素,skip就是傳遞給流水線中的下一個(gè)操作。limit的一個(gè)特點(diǎn)是,它不需要處理流中的所有元素,只要處理的元素個(gè)數(shù)達(dá)到maxSize,后面的元素就不需要處理了,這種可以提前結(jié)束的操作被稱為短路操作。

peek

peek的定義為:

Stream<T> peek(Consumer<? super T> action)

它返回的流與之前的流是一樣的,沒有變化,但它提供了一個(gè)Consumer,會(huì)將流中的每一個(gè)元素傳給該Consumer。這個(gè)方法的主要目的是支持調(diào)試,可以使用該方法觀察在流水線中流轉(zhuǎn)的元素,比如:

List<String> above90Names = students.stream()
        .filter(t->t.getScore()>90)
        .peek(System.out::println)
        .map(Student::getName)
        .collect(Collectors.toList());

mapToLong/mapToInt/mapToDouble

map函數(shù)接受的參數(shù)是一個(gè)Function<T, R>,為避免裝箱/拆箱,提高性能,Stream還有如下返回基本類型特定流的方法:

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)

DoubleStream/IntStream/LongStream是基本類型特定的流,有一些專門的更為高效的方法。比如,求學(xué)生列表的分?jǐn)?shù)總和,代碼可以為:

double sum = students.stream()
        .mapToDouble(Student::getScore)
        .sum();

flatMap                

flatMap的定義為:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

它接受一個(gè)函數(shù)mapper,對(duì)流中的每一個(gè)元素,mapper會(huì)將該元素轉(zhuǎn)換為一個(gè)流Stream,然后把新生成流的每一個(gè)元素傳遞給下一個(gè)操作。比如:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

List<String> lines = Arrays.asList(new String[]{        "hello abc",        "老馬  編程"
    });
List<String> words = lines.stream()
        .flatMap(line -> Arrays.stream(line.split("\\s+")))
        .collect(Collectors.toList());
System.out.println(words);

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

這里的mapper將一行字符串按空白符分隔為了一個(gè)單詞流,Arrays.stream可以將一個(gè)數(shù)組轉(zhuǎn)換為一個(gè)流,輸出為:

[hello, abc, 老馬, 編程]

可以看出,實(shí)際上,flatMap完成了一個(gè)1到n的映射。

針對(duì)基本類型,flatMap還有如下類似方法:

DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper)
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper)
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper)

終端操作

中間操作不觸發(fā)實(shí)際的執(zhí)行,返回值是Stream,而終端操作觸發(fā)執(zhí)行,返回一個(gè)具體的值,除了collect,Stream API的終端操作還有max, min, count, allMatch, anyMatch, noneMatch, findFirst, findAny, forEach, toArray, reduce等,我們逐個(gè)來看下。

max/min

max/min的定義為:

Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)

它們返回流中的最大值/最小值,值的注意的是,它的返回值類型是Optional<T>,而不是T。

java.util.Optional是Java 8引入的一個(gè)新類,它是一個(gè)泛型容器類,內(nèi)部只有一個(gè)類型為T的單一變量value,可能為null,也可能不為null。Optional有什么用呢?它用于準(zhǔn)確地傳遞程序的語(yǔ)義,它清楚地表明,其代表的值可能為null,程序員應(yīng)該進(jìn)行適當(dāng)?shù)奶幚怼?/span>

Optional定義了一些方法,比如:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

// value不為null時(shí)返回truepublic boolean isPresent()// 返回實(shí)際的值,如果為null,拋出異常NoSuchElementExceptionpublic T get()// 如果value不為null,返回value,否則返回otherpublic T orElse(T other)// 構(gòu)建一個(gè)空的Optional,value為nullpublic static<T> Optional<T> empty()// 構(gòu)建一個(gè)非空的Optional, 參數(shù)value不能為nullpublic static <T> Optional<T> of(T value)// 構(gòu)建一個(gè)Optional,參數(shù)value可以為null,也可以不為nullpublic static <T> Optional<T> ofNullable(T value)

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

在max/min的例子中,通過聲明返回值為Optional,我們就知道,具體的返回值不一定存在,這發(fā)生在流中不含任何元素的情況下。

看個(gè)簡(jiǎn)單的例子,返回分?jǐn)?shù)最高的學(xué)生,代碼可以為:

Student student = students.stream()
        .max(Comparator.comparing(Student::getScore).reversed())
        .get();

這里,假定students不為空。 

count

count很簡(jiǎn)單,就是返回流中元素的個(gè)數(shù)。比如,統(tǒng)計(jì)大于90分的學(xué)生個(gè)數(shù),代碼可以為:

long above90Count = students.stream()
        .filter(t->t.getScore()>90)
        .count();

allMatch/anyMatch/noneMatch

這幾個(gè)函數(shù)都接受一個(gè)謂詞Predicate,返回一個(gè)boolean值,用于判定流中的元素是否滿足一定的條件,它們的區(qū)別是:

  • allMatch: 只有在流中所有元素都滿足條件的情況下才返回true

  • anyMatch: 只要流中有一個(gè)元素滿足條件就返回true

  • noneMatch: 只有流中所有元素都不滿足條件才返回true

如果流為空,這幾個(gè)函數(shù)的返回值都是true。

比如,判斷是不是所有學(xué)生都及格了(不小于60分),代碼可以為:

boolean allPass = students.stream()
        .allMatch(t->t.getScore()>=60);

這幾個(gè)操作都是短路操作,都不一定需要處理所有元素就能得出結(jié)果,比如,對(duì)于allMatch,只要有一個(gè)元素不滿足條件,就能返回false。

findFirst/findAny

它們的定義為:

Optional<T> findFirst()
Optional<T> findAny()

它們的返回類型都是Optional,如果流為空,返回Optional.empty()。findFirst返回第一個(gè)元素,而findAny返回任一元素,它們都是短路操作。

隨便找一個(gè)不及格的學(xué)生,代碼可以為:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

Optional<Student> student = students.stream()
        .filter(t->t.getScore()<60)
        .findAny();if(student.isPresent()){    // 不及格的學(xué)生....}

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

forEach

有兩個(gè)foreach方法:

void forEach(Consumer<? super T> action)void forEachOrdered(Consumer<? super T> action)

它們都接受一個(gè)Consumer,對(duì)流中的每一個(gè)元素,傳遞元素給Consumer,區(qū)別在于,在并行流中,forEach不保證處理的順序,而forEachOrdered會(huì)保證按照流中元素的出現(xiàn)順序進(jìn)行處理。

比如,逐行打印大于90分的學(xué)生,代碼可以為:

students.stream()
        .filter(t->t.getScore()>90)
        .forEach(System.out::println);

toArray

toArray將流轉(zhuǎn)換為數(shù)組,有兩個(gè)方法:

Object[] toArray()<A> A[] toArray(IntFunction<A[]> generator)

不帶參數(shù)的toArray返回的數(shù)組類型為Object[],這經(jīng)常不是期望的結(jié)果,如果希望得到正確類型的數(shù)組,需要傳遞一個(gè)類型為IntFunction的generator,IntFunction的定義為:

public interface IntFunction<R> {
    R apply(int value);
}

generator接受的參數(shù)是流的元素個(gè)數(shù),它應(yīng)該返回對(duì)應(yīng)大小的正確類型的數(shù)組。

比如,獲取90分以上的學(xué)生數(shù)組,代碼可以為:

Student[] above90Arr = students.stream()
        .filter(t->t.getScore()>90)
        .toArray(Student[]::new);

Student[]::new就是一個(gè)類型為IntFunction<Student[]>的generator。

reduce

reduce代表歸約或者叫折疊,它是max/min/count的更為通用的函數(shù),將流中的元素歸約為一個(gè)值,有三個(gè)reduce函數(shù): 

Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);<U> U reduce(U identity,
    BiFunction<U, ? super T, U> accumulator,
    BinaryOperator<U> combiner);

第一個(gè)基本等同于調(diào)用:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

boolean foundAny = false;
T result = null;for (T element : this stream) {    if (!foundAny) {
        foundAny = true;
        result = element;
    }    else
        result = accumulator.apply(result, element);
}return foundAny ? Optional.of(result) : Optional.empty();

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

比如,使用reduce求

http://www.cnblogs.com/swiftma/p/7237442.html