<<java 8 函數(shù)式編程>>學(xué)習(xí) - 第三章 流

Java 8 對(duì)核心類庫的改進(jìn)主要包括集合類的 API 和新引入的流 (Stream)。流使程序員得以站在更高的抽象層次上對(duì)集合進(jìn)行操作.

1.從外部迭代到內(nèi)部迭代

使用 for 循環(huán)計(jì)算來自倫敦的藝術(shù)家人數(shù):

int count = 0;
for (Artist artist : allArtists) {
    if (artist.isFrom("London")) {
        count++;
    }
}

改進(jìn)一(外部迭代), 使用迭代器計(jì)算來自倫敦的藝術(shù)家人數(shù):

int count = 0;
Iterator<Artist> iterator = allArtists.iterator(); 
while(iterator.hasNext()) {
    Artist artist = iterator.next(); 
    if (artist.isFrom("London")) {
        count++; 
    }
}

改進(jìn)二(內(nèi)部迭代), 利用stream計(jì)算來自倫敦的藝術(shù)家人數(shù):

long count = allArtists.stream()
    .filter(artist -> artist.isFrom("London"))
    .count();

Stream 是用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具.
上述代碼可被分解為兩步更簡單的操作:

  • 找出所有來自倫敦的藝術(shù)家;
  • 計(jì)算他們的人數(shù).

每種操作都對(duì)應(yīng) Stream 接口的一個(gè)方法. 為了找出來自倫敦的藝術(shù)家, 需要對(duì) Stream 對(duì)象進(jìn)行過濾:filter. 過濾在這里是指"只保留通過某項(xiàng)測(cè)試的對(duì)象". 測(cè)試由一個(gè)函數(shù)完成, 根據(jù)藝術(shù)家是否來自倫敦, 該函數(shù)返回true或者false. 由于Stream API的函數(shù)式編程風(fēng)格, 我們并沒有改變集合的內(nèi)容, 而是描述出 Stream 里的內(nèi)容. count() 方法計(jì)算給定 Stream 里包含多少個(gè)對(duì)象.

2.實(shí)現(xiàn)機(jī)制

上述整個(gè)過程被分解為兩種更簡單的操作: 過濾和計(jì)數(shù), 看似有化簡為繁之嫌--兩次操作(filter和count)是否需要兩次循環(huán)? 事實(shí)上類庫設(shè)計(jì)精妙, 只需對(duì)藝術(shù)家列表迭代一次.

通常, 在 Java 中調(diào)用一個(gè)方法,計(jì)算機(jī)會(huì)隨即執(zhí)行操作:比如, System.out.println ("Hello World"); 會(huì)在終端上輸出一條信息. 這種方式叫做及早求值. 而Stream對(duì)象則則使用了另一個(gè)概念叫做惰性求值, 直到真正需要時(shí)才進(jìn)行計(jì)算.

//只過濾不計(jì)數(shù)
allArtists.stream()
    .filter(artist -> artist.isFrom("London"));

這行代碼并未做什么實(shí)際性的工作, filter 只刻畫出了 Stream, 但沒有產(chǎn)生新的集合.
判斷一個(gè)操作是惰性求值還是及早求值很簡單, 只需看它的返回值: 如果返回值是 Stream, 那么是惰性求值; 如果返回值是另一個(gè)值或?yàn)榭? 那么就是及早求值.

3.常用的流操作

3.1 collect(toList())

collect(toList()) 方法由 Stream 里的值生成一個(gè)列表,是一個(gè)及早求值操作.

List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);

3.2 map

如果有一個(gè)函數(shù)可以將一種類型的值轉(zhuǎn)換成另外一種類型, map 操作就可以使用該函數(shù), 將一個(gè)流中的值轉(zhuǎn)換成一個(gè)新的流.

//使用 map 操作將字符串轉(zhuǎn)換為大寫形式
List<String> collected = Stream.of("a", "b", "hello")
                                    .map(string -> string.toUpperCase())
                                    .collect(Collectors.toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);

3.3 filter

遍歷數(shù)據(jù)并檢查其中的元素時(shí),可嘗試使用 Stream 中提供的新方法 filter.

List<String> beginningWithNumbers
                = Stream.of("a", "1abc", "abc1")
                .filter(value -> Character.isDigit(value.charAt(0)))
                .collect(Collectors.toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);

filter 接受一個(gè)函數(shù)作為參數(shù), 該函數(shù)用 Lambda 表達(dá)式表示, 其返回值必須是 true 或者 false, 該 Lambda 表達(dá)式的函數(shù)接口正是之前介紹過的 Predicate.

3.4 flatMap

flatMap 方法可用 Stream 替換值,然后將多個(gè) Stream 連接成一個(gè) Stream.

//假設(shè)有一個(gè)包含多個(gè)列表的流, 現(xiàn)在希望得到所有數(shù)字的序列.
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
                .flatMap(numbers -> numbers.stream())
                .collect(Collectors.toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);

3.5 max和min

Stream上常用的操作之一是求最大值和最小值. Stream API中的max和min操作足以解決這一問題.

//查找長度最小的元素
List<String> tracks = Arrays.asList("Bakai", "Violets for Your Furs","Time Was");
String shortestTrack = tracks.stream()
        .min(Comparator.comparing(track -> track.length()))
        .get();
assertEquals(tracks.get(1), shortestTrack);

3.6 reduce

reduce 操作可以實(shí)現(xiàn)從一組值中生成一個(gè)值. 在上述例子中用到的 count、min 和 max 方 法, 因?yàn)槌S枚患{入標(biāo)準(zhǔn)庫中. 事實(shí)上, 這些方法都是 reduce 操作.
下例展示這一過程, Lambda 表達(dá)式就是 reducer, 它執(zhí)行求和操作, 有兩個(gè)參數(shù): 傳入 Stream 中的當(dāng)前元素和 acc. 將兩個(gè)參數(shù)相加, acc 是累加器, 保存著當(dāng)前的累加結(jié)果.

//使用 reduce 求和
int count = Stream.of(1, 2, 3)
                .reduce(0, (acc, element) -> acc + element);
assertEquals(6, count);

4.重構(gòu)遺留代碼

為了進(jìn)一步闡釋如何重構(gòu)遺留代碼, 本節(jié)將舉例說明如何將一段使用循環(huán)進(jìn)行集合操作的代碼, 重構(gòu)成基于 Stream 的操作.
下面是跟專輯相關(guān)的一組基礎(chǔ)類:

package com.chyun.java8.lambda.base;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
 * Domain class for a popular music artist.
 *
 * @author Richard Warburton
 */
public final class Artist {

    private String name;
    private List<Artist> members;
    private String nationality;

    public Artist(String name, String nationality) {
        this(name, Collections.emptyList(), nationality);
    }

    public Artist(String name, List<Artist> members, String nationality) {
        Objects.requireNonNull(name);
        Objects.requireNonNull(members);
        Objects.requireNonNull(nationality);
        this.name = name;
        this.members = new ArrayList<>(members);
        this.nationality = nationality;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @return the members
     */
    public Stream<Artist> getMembers() {
        return members.stream();
    }

    /**
     * @return the nationality
     */
    public String getNationality() {
        return nationality;
    }

    public boolean isSolo() {
        return members.isEmpty();
    }

    public boolean isFrom(String nationality) {
        return this.nationality.equals(nationality);
    }

    @Override
    public String toString() {
        return getName();
    }

    public Artist copy() {
        List<Artist> members = getMembers().map(Artist::copy).collect(toList());
        return new Artist(name, members, nationality);
    }

}
package com.chyun.java8.lambda.base;

import java.util.stream.Stream;

import static java.util.stream.Stream.concat;

public interface Performance {

    public String getName();

    public Stream<Artist> getMusicians();

    // TODO: test
    public default Stream<Artist> getAllMusicians() {
        return getMusicians().flatMap(artist -> {
            return concat(Stream.of(artist), artist.getMembers());
        });
    }

}
package com.chyun.java8.lambda.base;

public final class Track {

    private final String name;
    private final int length;

    public Track(String name, int length) {
        this.name = name;
        this.length = length;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @return the length of the track in milliseconds.
     */
    public int getLength() {
        return length;
    }

    public Track copy() {
        return new Track(name, length);
    }

}
package com.chyun.java8.lambda.base;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;

/**
 *
 * @author richard
 */
public final class Album implements Performance {

    private String name;
    private List<Track> tracks;
    private List<Artist> musicians;

    public Album(String name, List<Track> tracks, List<Artist> musicians) {
        Objects.requireNonNull(name);
        Objects.requireNonNull(tracks);
        Objects.requireNonNull(musicians);

        this.name = name;
        this.tracks = new ArrayList<>(tracks);
        this.musicians = new ArrayList<>(musicians);
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @return the tracks
     */
    public Stream<Track> getTracks() {
        return tracks.stream();
    }

    /**
     * Used in imperative code examples that need to iterate over a list
     */
    public List<Track> getTrackList() {
        return unmodifiableList(tracks);
    }

    /**
     * @return the musicians
     */
    public Stream<Artist> getMusicians() {
        return musicians.stream();
    }

    /**
     * Used in imperative code examples that need to iterate over a list
     */
    public List<Artist> getMusicianList() {
        return unmodifiableList(musicians);
    }

    public Artist getMainMusician() {
        return musicians.get(0);
    }

    public Album copy() {
        List<Track> tracks = getTracks().map(Track::copy).collect(toList());
        List<Artist> musicians = getMusicians().map(Artist::copy).collect(toList());
        return new Album(name, tracks, musicians);
    }

}

假定選定一組專輯, 找出其中所有長度大于 1 分鐘的曲目名稱.

//遺留代碼, 使用for循環(huán)
public Set<String> findLongTracks(List<Album> albums) { 
    Set<String> trackNames = new HashSet<>();
    for(Album album : albums) {
        for (Track track : album.getTrackList()) { 
            if (track.getLength() > 60) {
                String name = track.getName();
                trackNames.add(name);
            }
        } 
    }
    return trackNames;
}

第一步要修改的是 for 循環(huán). 首先使用 Stream 的 forEach 方法替換掉 for 循環(huán).

public Set<String> findLongTracks(List<Album> albums) {
    Set<String> trackNames = new HashSet<>();
    albums.stream()
        .forEach(album -> {
            album.getTracks()
                    .forEach(track -> {
                        if (track.getLength() > 60) {
                            String name = track.getName();
                            trackNames.add(name);
                        }
                    });
        });
    return trackNames; 
}

第二步, 將內(nèi)部的 forEach 方法用Stream代替.

public Set<String> findLongTracks(List<Album> albums) { 
    Set<String> trackNames = new HashSet<>(); 
    albums.stream()
        .forEach(album -> {
            album.getTracks()
                    .filter(track -> track.getLength() > 60)
                    .map(track -> track.getName())
                    .forEach(name -> trackNames.add(name));
        });
    return trackNames;
}

第三步,用flatMap替換第一個(gè)foreach.

public Set<String> findLongTracks(List<Album> albums) {
    Set<String> trackNames = new HashSet<>();
    albums.stream()
            .flatMap(album -> album.getTracks())
            .filter(track -> track.getLength() > 60)
            .map(track -> track.getName())
            .forEach(name -> trackNames.add(name));
    return trackNames;
}

第四步, 用collect()方法替換最后的forEach.

public Set<String> findLongTracks(List<Album> albums) {
    return albums.stream()
            .flatMap(album -> album.getTracks())
            .filter(track -> track.getLength() > 60)
            .map(track -> track.getName())
            .collect(Collectors.toSet());
}

5.練習(xí)

a.編寫一個(gè)求和函數(shù), 計(jì)算流中所有數(shù)之和;

public int addUp(Stream<Integer> numbers) {
    return numbers.reduce(0, (acc, x) -> acc + x);
}

b.編寫一個(gè)函數(shù), 接受藝術(shù)家列表作為參數(shù), 返回一個(gè)字符串列表, 其中包含藝術(shù)家的姓名和國籍;

public static List<String> getNamesAndOrigins(List<Artist> artists) {
    return artists.stream().flatMap(artist -> Stream.of(artist.getName(), artist.getNationality()))
            .collect(Collectors.toList());
}

c.修改如下代碼,將外部迭代轉(zhuǎn)換成內(nèi)部迭代;

int totalMembers = 0;
for (Artist artist : artists) {
    Stream<Artist> members = artist.getMembers();
    totalMembers += members.count();
}
artists.stream().map(artist -> artist.getMembers().count()).reduce(0L, Long::sum).intValue();

d.在一個(gè)字符串列表中, 找出包含最多小寫字母的字符串.

public static Optional<String> mostLowercaseString(List<String> strings) {
    return strings.stream().max(Comparator.comparing(string -> (int) string.chars().filter(Character::isLowerCase).count()));
}

e.只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 map 操作的代碼, 如果不想返回 Stream, 可以返回一個(gè) List.

public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
    return stream.reduce(new ArrayList<O>(), (acc, x) -> {
        List<O> newAcc = new ArrayList<>(acc);
        newAcc.add(mapper.apply(x));
        return newAcc;
    }, (List<O> left, List<O> right) -> {
        List<O> newleft = new ArrayList<>(left);
        newleft.addAll(right);
        return newleft;
    });
}

此處關(guān)于reduce可以參考https://segmentfault.com/q/1010000004944450.
f.只用 reduce 和 Lambda 表達(dá)式寫出實(shí)現(xiàn) Stream 上的 filter 操作的代碼, 如果不想返回 Stream, 可以返回一個(gè) List.

public static <O> List<O> filter(Stream<O> stream, Predicate<O> mapper) {
    return stream.reduce(new ArrayList<O>(), (acc, x) -> {
        if (mapper.test(x)) {
            List<O> newAcc = new ArrayList<>(acc);
            newAcc.add(x);
            return newAcc;
        }
        return acc;
    }, (List<O> left, List<O> right) -> {
        List<O> newleft = new ArrayList<>(left);
        newleft.addAll(right);
        return newleft;
    });
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末分俯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哆料,更是在濱河造成了極大的恐慌,老刑警劉巖东亦,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異典阵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壮啊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歹啼,“玉大人玄渗,你說我怎么就攤上這事狸眼。” “怎么了拓萌?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我甜紫,道長,這世上最難降的妖魔是什么囚霸? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任激才,我火速辦了婚禮拓型,結(jié)果婚禮上瘸恼,老公的妹妹穿的比我還像新娘。我一直安慰自己东帅,他們只是感情好压固,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布靠闭。 她就那樣靜靜地躺著,像睡著了一般愧膀。 火紅的嫁衣襯著肌膚如雪拦键。 梳的紋絲不亂的頭發(fā)上檩淋,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音蟀悦,去河邊找鬼。 笑死日戈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涎拉。 我是一名探鬼主播瑞侮,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鼓拧,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了季俩?” 一聲冷哼從身側(cè)響起钮糖,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎店归,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體消痛,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年逞带,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纱新。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖遇汞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情空入,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布执庐,位于F島的核電站,受9級(jí)特大地震影響导梆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜看尼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藏斩。 院中可真熱鬧,春花似錦狰域、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抬探。三九已至子巾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間线梗,已是汗流浹背椰于。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工仪搔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人僻造。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像髓削,于是被迫代替她去往敵國和親镀娶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子立膛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Jav8中梯码,在核心類庫中引入了新的概念宝泵,流(Stream)轩娶。流使得程序媛們得以站在更高的抽象層次上對(duì)集合進(jìn)行操作儿奶。...
    仁昌居士閱讀 3,619評(píng)論 0 6
  • 第一章 為什么要關(guān)心Java 8 使用Stream庫來選擇最佳低級(jí)執(zhí)行機(jī)制可以避免使用Synchronized(同...
    謝隨安閱讀 1,482評(píng)論 0 4
  • 01 何小萍和劉峰在片首最先出現(xiàn)许溅,畫面是劉峰將新入伍的何小萍接到部隊(duì)瓤鼻。 快進(jìn)門時(shí)贤重,劉峰提起何小萍的父親正在勞動(dòng)改教...
    說心理話閱讀 1,152評(píng)論 0 2
  • 五萬元
    十七k閱讀 121評(píng)論 0 0
  • 數(shù)量與質(zhì)量 數(shù)量與質(zhì)量能否兼得不能一概而論。 人和人不同并蝗,世界上確有很多天才祭犯,能夠一直兼顧數(shù)量與質(zhì)量,這是我等普通...
    zzzzzzzzzzzzzzc閱讀 232評(píng)論 0 2