Java 8 學(xué)習(xí)筆記

第一章

為什么要關(guān)心Java 8

使用Stream庫來選擇最佳低級(jí)執(zhí)行機(jī)制可以避免使用Synchronized(同步)來編寫代碼娱两,這種代碼不僅容易出錯(cuò)讨越,并且在多核CPU上的執(zhí)行成本也很高。
因?yàn)槎嗪薈PU每個(gè)處理器內(nèi)核都有獨(dú)立的高速緩存暖侨。加鎖需要這些高速緩存同步運(yùn)行,而這又需要內(nèi)核間進(jìn)行緩慢的緩存一致性協(xié)議通信

Streams的作用不僅僅是把代碼傳遞給方法伏蚊,它提供了一種新的間接地表達(dá)行為參數(shù)化的方法。
比如格粪,對(duì)于兩個(gè)只有幾行代碼不同的方法躏吊,只需要把不同的那部分代碼作為參數(shù)傳遞進(jìn)去就可以氛改。

流處理

流是一系列數(shù)據(jù)項(xiàng),一次只生成一項(xiàng)比伏。
基于Unix操作流的思想胜卤,Java 8在java.util.stream中添加了一個(gè)Stream APIStream API的很多方法可以鏈接起來形成一個(gè)復(fù)雜的流水線赁项。
Java 8可以透明的把輸入的不相關(guān)部分拿到幾個(gè)CPU內(nèi)核上去分別執(zhí)行Stream操作流水線葛躏,并不需要Thread

Unix操作流示例:
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3 將兩個(gè)文件連接起來創(chuàng)建一個(gè)流悠菜,tr會(huì)轉(zhuǎn)換流中的字符舰攒,sort會(huì)對(duì)流中的行進(jìn)行排序,而tail -3則給出流的最后三行悔醋,這些程序通過管道(|)連接在一起摩窃。

用行為參數(shù)化把代碼傳遞給方法

Java 8增加了通過API來傳遞代碼的能力,把方法(代碼)作為參數(shù)傳遞給另一個(gè)方法芬骄。

并行與共享的可變數(shù)據(jù)

要能夠同時(shí)對(duì)不同的輸入安全地執(zhí)行猾愿,這意味著寫代碼不能訪問共享的可變數(shù)據(jù)。雖然可以使用Synchronized來打破“不能有共享的可變數(shù)據(jù)”的規(guī)則账阻,但是同步迫使代碼按照順序執(zhí)行蒂秘,這與并行處理相悖。

沒有共享的可變數(shù)據(jù)將方法和函數(shù)(即代碼)傳遞給其他方法的能力是函數(shù)式編程范式的基石宰僧。

“不能有共享的可變數(shù)據(jù)”的要求意味著材彪,一個(gè)方法要通過它將參數(shù)值轉(zhuǎn)換為結(jié)果的方法完全描述的,就像是一個(gè)數(shù)學(xué)函數(shù)琴儿,沒有可見的副作用段化。

Java中的函數(shù)

Java 可能操作的值: 原始值,對(duì)象(嚴(yán)格來說是對(duì)象的引用)造成。其他的結(jié)構(gòu)(二等公民)雖然有助于表示值的結(jié)構(gòu)显熏,但它們程序執(zhí)行期間并不能傳遞。程序之間期間能傳遞的是(一等公民)晒屎,解決辦法是用方法來定義類喘蟆,通過類的實(shí)例化產(chǎn)生值,人們又發(fā)現(xiàn)鼓鲁, 通過在運(yùn)行時(shí)傳遞方法*能夠?qū)⒎椒ㄗ兂芍祦韨鬟f蕴轨。

讓方法等概念作為值(一等公民)會(huì)讓編程變得容易很多。

方法和Lambda作為一等公民

Java 8的一個(gè)新功能是方法引用骇吭。
要將方法作為值傳給另一個(gè)方法橙弱,只需用方法引用::語法(即“把這個(gè)方法作為值”)將其傳給另一個(gè)方法即可。
這樣做的好處是代碼讀起來更接近問題的陳述,方法生成升為了“一等公民”棘脐。

與對(duì)象引用傳遞對(duì)象類似(對(duì)象引用使用new創(chuàng)建)斜筐,當(dāng)寫下XXX:MethodName的時(shí)候,就創(chuàng)建了一個(gè)方法引用蛀缝,同樣也可以傳遞它顷链。以前的Java版本只能把方法包裹在FileFilter對(duì)象里,然后才能傳遞給別的方法屈梁。

除了允許函數(shù)成為一等值外嗤练,Java 8還體現(xiàn)了更廣義的將函數(shù)作為值的思想------Lambda

函數(shù)式編程風(fēng)格,即編寫把函數(shù)作為一等值來傳遞的程序

傳遞代碼的例子

假設(shè)有一個(gè)Apple類在讶,它有一個(gè)getColor方法潭苞,還有一個(gè)變量inventory保存著一個(gè)Apples的列表。要選出所有的綠蘋果并返回一個(gè)列表真朗。在Java 8之前此疹,可能會(huì)寫一個(gè)這樣的方法:

public static List<Apple> filterGreenApples(List<Apple> inventory){
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory){
        //代碼會(huì)僅僅選出綠色蘋果,并加入result中
        if ("green".equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

接下來遮婶,又想要選出重的蘋果蝗碎,比如超過150克的,于是又寫了如下方法:

public static List<Apple> filterGreenApples(List<Apple> inventory){
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory){
        //代碼會(huì)僅僅選出重量大于蘋果旗扑,并加入result中
        if (apple.equals(apple.getWeight() > 150 ) {
            result.add(apple);
        }
    }
    return result;
}

這兩個(gè)方法只有一行不同蹦骑,Java 8由于可以把條件代碼作為參數(shù)傳遞進(jìn)去,這樣可以避免filter方法出現(xiàn)重復(fù)的代碼臀防。于是可以這樣寫:

    public static boolean isGreenApple(Apple apple) {
        return "green".equals(apple.getColor()); 
    }

    public static boolean isHeavyApple(Apple apple) {
        return apple.getWeight() > 150;
    }

    public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }       

注意filterApples的參數(shù): Predicate<Apple> p眠菇,這里是從java.util.function.predicate導(dǎo)入的,它的作用是定義一個(gè)泛型接口:

public interface Predicate<T> {
    boolean test(T t);
}

通過這樣袱衷,可以將方法通過謂語(Predicate)參數(shù)p傳遞進(jìn)filterApples捎废。

要使用 filterApples 的話,可以寫成這樣:

List<Apple> greenApples = filterApples(inventory, FilteringApples::isGreenApple);

或者

 List<Apple> heavyApples = filterApples(inventory, FilteringApples::isHeavyApple);

Predicate(謂語)在數(shù)學(xué)上常用來代表一個(gè)類似函數(shù)的東西致燥,它接受一個(gè)參數(shù)值登疗,并返回true或false。

從傳遞方法到Lamda

除了把方法作為值來傳遞以外嫌蚤,Java 8還引入了一套記法(匿名函數(shù)或Lambda)辐益,于是上面的代碼可以寫成:

List<Apple> greenApples2 = filterApples(inventory, 
(Apple a) -> "green".equals(a.getColor()));

或者

List<Apple> heavyApples2 = filterApples(inventory, 
(Apple a) -> a.getWeight() > 150);

甚至

List<Apple> weirdApples = filterApples(inventory,
 (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()));

通過這種方法,你甚至不需要為只用一次的方法寫定義脱吱;代碼更干凈清晰智政。

Q:什么時(shí)候使用方法作為值傳遞,什么時(shí)候使用Lambda箱蝠?

A:要是代碼的長度多于幾行续捂,使用Lambda表示的效果并不是一目了然猜年,這樣還是應(yīng)該使用方法引用來指向一個(gè)方法,而不是使用匿名的Lambda疾忍。簡言之,使用哪種方法應(yīng)該以代碼的清晰度為準(zhǔn)繩床三。

通過Stream和Lamdba表達(dá)式一罩,可以將之前的篩選代碼改進(jìn)成如下形式:

import static java.util.stream.Collector.toList;
List<Apple> heavyApples =
  inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(toList());

并行處理:

import static java.util.stream.Collector.toList;
List<Apple> heavyApples =
  inventory.parallelstream().filter((Apple a) -> a.getWeight() > 150).collect(toList());

和Collection API相比,StreamAPI處理數(shù)據(jù)的方式非常不同撇簿,用集合需要自己去做迭代的過程聂渊。你需要用for-each循環(huán)一個(gè)個(gè)去迭代元素、處理元素四瘫,這種數(shù)據(jù)迭代的方法被稱為外部迭代汉嗽。而有了Stream API,數(shù)據(jù)處理完全在庫內(nèi)部進(jìn)行找蜜,這種迭代思想被稱為內(nèi)部迭代饼暑。

計(jì)算集群:用高速網(wǎng)絡(luò)連接起來的多臺(tái)計(jì)算機(jī)

Colletion主要是為了存儲(chǔ)和訪問數(shù)據(jù),而Stream則主要用于描述對(duì)數(shù)據(jù)的計(jì)算洗做。

第二章

通過行為參數(shù)化傳遞代碼

行為參數(shù)化是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式弓叛。
將代碼塊作為參數(shù)傳遞給另一個(gè)方法,稍后再去執(zhí)行它诚纸。這樣這個(gè)方法就基于那塊代碼被參數(shù)化了撰筷。打個(gè)比方,為了實(shí)現(xiàn)行為參數(shù)化可能會(huì)這樣處理一個(gè)集合:

  • 可以對(duì)列表中的每個(gè)元素做 “某件事”
  • 可以再列表處理完后做 “另一件事”
  • 遇到錯(cuò)誤時(shí)可以做 “另外一件事”

這樣一個(gè)方法便可以接受不同的新行為作為參數(shù)去執(zhí)行畦徘。

舉個(gè)栗子:假設(shè)要求你對(duì)蘋果的不同屬性做篩選毕籽,比如大小、形狀井辆、產(chǎn)地关筒、重量等,寫好多個(gè)重復(fù)的filter方法或者一個(gè)巨大的非常復(fù)雜的方法都不是好辦法杯缺。為此需要一種更好的方法平委,來把蘋果的選擇標(biāo)準(zhǔn)告訴filterApples方法。更高層次的抽象這個(gè)問題夺谁,對(duì)選擇標(biāo)準(zhǔn)進(jìn)行建模:蘋果需要根據(jù)Apple的某些屬性來返回一個(gè)boolean值廉赔,這需要一個(gè)返回boolean值的函數(shù),我們把它稱為謂詞匾鸥。

現(xiàn)在我們定義一個(gè)接口來對(duì)選擇標(biāo)準(zhǔn)建模:

public interface ApplePredicate(){
    boolean test(Apple apple);
}

現(xiàn)在就可以用ApplePredicate的多個(gè)實(shí)現(xiàn)代表不同的選擇標(biāo)準(zhǔn)了:

    static class AppleWeightPredicate implements ApplePredicate{
        public boolean test(Apple apple){
            return apple.getWeight() > 150; 
        }
    }
    static class AppleColorPredicate implements ApplePredicate{
        public boolean test(Apple apple){
            return "green".equals(apple.getColor());
        }
    }

    static class AppleRedAndHeavyPredicate implements ApplePredicate{
        public boolean test(Apple apple){
            return "red".equals(apple.getColor()) 
                    && apple.getWeight() > 150; 
        }
    }
選擇蘋果的不同策略

這些標(biāo)準(zhǔn)可以看做filter方法的不同行為蜡塌。上面做的這些和“策略設(shè)計(jì)模式”相關(guān),它讓你定義一族算法勿负,把它們封裝起來馏艾,然后運(yùn)行的時(shí)候選擇一個(gè)算法劳曹,這里的算法族就是applePredicate,不同的策略就是AppleHeavyWeightPredicate和AppleGreenColorPredicate琅摩。

下面的問題是铁孵,要怎么利用applePredicate的不同實(shí)現(xiàn)。需要讓filterApples方法接受applePredicate對(duì)象房资,對(duì)Apple做條件測試蜕劝。這就是行為參數(shù)化:讓方法接受多種行為作為參數(shù),并在內(nèi)部使用轰异,來完成不同的行為岖沛。

為此需要給filterApples方法添加一個(gè)參數(shù),讓它能接受ApplePredicate對(duì)象搭独。這樣在軟件工程上帶來的好處就是:**將filterApples方法迭代集合的邏輯與你要應(yīng)用到集合中每個(gè)也元素的行為(這里是謂詞)區(qū)分開了婴削。

由此便可以修改ApplePredicate方法為:

    public static List<Apple> filterApples(List<Apple> inventory,
                                           ApplePredicate p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple: inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }

現(xiàn)在便可以創(chuàng)建不同的ApplePredicate對(duì)象,并將它們傳遞給filterApples方法牙肝。filterApples方法的行為取決于你通過ApplePredicate對(duì)象傳遞的代碼唉俗。實(shí)現(xiàn)了行為參數(shù)化

雖然已經(jīng)可以把行為抽象出來讓代碼適應(yīng)需求的變化了配椭,但是這個(gè)過程很啰嗦互躬,因?yàn)楫?dāng)要把新的行為傳遞給方法的時(shí)候,可能需要聲明很多實(shí)現(xiàn)接口的類來實(shí)例化好幾個(gè)只要實(shí)例化一次的類(劃重點(diǎn)颂郎,這邊說的是對(duì)于只要實(shí)例化一次的類)吼渡。這樣又啰嗦又費(fèi)時(shí)間。

對(duì)于這個(gè)問題也有對(duì)應(yīng)的解決辦法乓序,Java有一個(gè)機(jī)制稱為匿名類寺酪,它可以讓你同時(shí)聲明和實(shí)例化一個(gè)類,使代碼更為簡潔替劈。

匿名類

匿名類和Java局部類差不多寄雀,但匿名類沒有名字。它允許你同時(shí)聲明并實(shí)例化一個(gè)類(隨用隨建)陨献。

用匿名類實(shí)現(xiàn)的ApplePredicate對(duì)象:

List<Apple> redApples= filterApples(inventory, new ApplePredicate(){
    public boolean test(Apple apple){
        return "red".euqls(apple.getColor());
    }
});

但是這也并不完全令人滿意盒犹。第一,它代碼占用很多行眨业。第二急膀,它用起來容易讓人費(fèi)解。即使匿名類處理在某種程度上改善了為一個(gè)借口聲明好幾個(gè)實(shí)體類的啰嗦問題龄捡,讓仍不能讓人滿意卓嫂。更好的方式是通過Lambda表達(dá)式讓代碼更易讀。

上面的代碼在Java8里可以用Lambda表達(dá)式重寫為下面的樣子:

List<Apple> result =
        filterApples(inventory, (Apple apple) -> "red".equals(getColor()));
將List類型抽象化

目前的filterApples方法還只適用于Apple聘殖。還可以將List類型抽象化晨雳,從而超越眼前要處理的問題:

public interface Predicate<T>{
    boolean test(T t);
}

public static <T> List<T> filter(List<T> inventory, ApplePredicate<T> p){
    List<T> result = new ArrayList<>();
    for(T e : list ){
        if(p.test(e)){
            result.add(e);
        }
    }
    return result;
} 

現(xiàn)在可以把filter方法用在其他列表上了行瑞。

真實(shí)的例子

下面將通過兩個(gè)例子來鞏固傳遞代碼的思想。

用Comparator來排序

想要根據(jù)不同的屬性使用一種方法來表示和使用不同的排序行為來輕松地適應(yīng)變化的需求餐禁。
在Java 8中血久,List自帶了一個(gè)sort方法(也可以使用Collections.sort)。sort的行為可以用java.util.Comparator對(duì)象來參數(shù)化帮非,它的接口如下:

public interface Comparator<T>{
    public int compare(T o1,T o2);
}

因此可以隨時(shí)創(chuàng)建Comparator的實(shí)現(xiàn)氧吐,用sort方法表現(xiàn)出不同的行為。

按照重量升序?qū)齑媾判颍?/p>

inventory.sort(new Comparator<Apple>(){
    public int compare(Apple a1,Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

(這段代碼并沒有告訴你如何同時(shí)遍歷列表中的兩個(gè)元素喜鼓,不要糾結(jié)遍歷問題,下一章會(huì)詳細(xì)的講解如何編寫和使用Lambda表達(dá)式衔肢。)

用Runnable執(zhí)行代碼塊

線程就像是輕量級(jí)的進(jìn)程:它們自己執(zhí)行一個(gè)代碼塊庄岖。多個(gè)線程可能會(huì)運(yùn)行不同的代碼。為此需要一種方式來代表要執(zhí)行的一段代碼角骤。在Java里隅忿,可以使用Runnable接口表示一個(gè)要執(zhí)行的代碼塊:

public interface Runnabke{
    public void run();
}

可以像下面這樣使用這個(gè)接口創(chuàng)建執(zhí)行不同行為的線程:

Thread t = new Thread(new Runnable()){
    public void run(){
        System.out.println("Hello world");
    }
});

用Lambda表達(dá)式的話,看起來會(huì)是這樣:

Thread t =new Thread(() -> System.out.println("Hello world"));

第三章

Lambda表達(dá)式

利用行為參數(shù)化這個(gè)概念邦尊,就可以編寫更為靈活且可重復(fù)使用的代碼背桐。但同時(shí),使用匿名類來表示不同的行為并不令人滿意蝉揍。Java 8引入了Lambda表達(dá)式來解決這個(gè)問題链峭。它使你以一種很簡潔的表示一個(gè)行為或傳遞代碼。

可以將Lambda表達(dá)式理解為簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱又沾,但它有參數(shù)列表弊仪、函數(shù)主體、返回類型杖刷,可能還有一個(gè)可以拋出的異常列表励饵。

  • 匿名 - 因?yàn)樗幌衿胀ǖ姆椒ㄒ粯佑幸粋€(gè)明確的名稱。
  • 函數(shù) - 說它是函數(shù)是因?yàn)長ambda函數(shù)不像方法那樣屬于某個(gè)特定的類滑燃,但和方法要一樣役听,Lambda有參數(shù)列表、函數(shù)主體表窘、返回類型典予,還可能有可以拋出的異常列表。
  • 傳遞 - Lambda表達(dá)式可以作為參數(shù)傳遞給方法或存儲(chǔ)在變量中乐严。
  • 簡潔 - 無需像匿名類那樣寫很多模板代碼

使用Lambda的最終結(jié)果就是你的代碼變得更清晰熙参、靈活。打比方麦备,利用Lambda表達(dá)式孽椰,可以更為簡潔地自定義一個(gè)Comparator對(duì)象昭娩。

Lambda表達(dá)式由參數(shù)、箭頭和主體組成

對(duì)比以下兩段代碼:

Comparator <Apple> byWeight = new Comparator<Apple>(){
    public int compare(Apple a1,Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
};
(Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

代碼看起來更清晰了黍匾±该欤基本上只傳遞了真正需要的代碼(compare方法的主體)。

Lambda表達(dá)式由三個(gè)部分锐涯,如圖 Lambda表達(dá)式由參數(shù)磕诊、箭頭和主體組成 所示

  • 參數(shù)列表 - 這里它采用了Comparator中compare方法的參數(shù),兩個(gè)Apple纹腌。
  • 箭頭 - 箭頭 -> 把參數(shù)列表與Lambda主體分隔開霎终。
  • Lambda主體 - 比較兩個(gè)Apple的重量。表達(dá)式就是Lambda的返回值升薯。

Lambda 的基本語法是:
(parameters) -> expression(parameters) -> { statements; }

函數(shù)式接口

之前的Predicate<T>就是一個(gè)函數(shù)式接口莱褒,因?yàn)镻redicate僅僅定義了一個(gè)抽象方法:

public interface Predicate<T>{
    boolean test(T t);
}

簡而言之,函數(shù)式接口就是**只定義一個(gè)抽象方法的接口涎劈。Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn)广凸,并把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例。

函數(shù)式接口的抽象方法的簽名

把Lambda付諸實(shí)踐:環(huán)繞執(zhí)行模式

要從一個(gè)文件中讀取一行所需的模板代碼:

public static String processFile() throws IOException{
    try (BufferedReader br =
            new BufferedReader(new FileReader("data.txt"))){
        return br.readLine();
    }
}

現(xiàn)在這段代碼的局限性在于只能讀文件的第一行蛛枚,如果想要返回頭兩行谅海,甚至是返回使用最頻繁的詞。這時(shí)需要把processFile的行為參數(shù)化蹦浦。
需要一個(gè)接收BufferedReader并返回String的Lambda扭吁。下面是從BufferedReader中打印兩行的寫法:

String result = processFile(BufferedReader br) -> br.readLine() + br.readLine());

現(xiàn)在需要?jiǎng)?chuàng)建一個(gè)能匹配BufferedReader -> String,還可以拋出IOException異常的接口:

@FunctionalInterface
public interface BufferedReaderProcessor{
    String process(BufferedReader b) throws IOException;
}

@FunctionalInterface 是什么盲镶?
這個(gè)標(biāo)注用于表示該接口會(huì)設(shè)計(jì)成一個(gè)函數(shù)式接口智末。如果用 @FunctionalInterface 定義了一個(gè)接口,而它卻不是函數(shù)式接口的話徒河,編譯器將返回一個(gè)提示原因的錯(cuò)誤系馆。
使用它不是必須的,但是使用它是比較好的做法顽照。

現(xiàn)在就可以把這個(gè)接口作為新的processFile方法的參數(shù)由蘑,在方法主體內(nèi),對(duì)得到BufferedReaderProcessor對(duì)象調(diào)用process方法執(zhí)行處理:

public static String processFile(BufferedReaderProcessor p) throws IOException {
    try (BufferedReader br =
                 new BufferedReader(new FileReader("data.txt"))){
        return p.process(br);
    }
}

現(xiàn)在可以通過傳遞不同的Lambda重用processFile方法代兵,并以不用的方式處理文件了尼酿。
處理一行:

String oneLine =
    processFile((BufferedReader br) -> br.readLine());

處理兩行:

String twoLines = 
     processFile((BufferedReader br) -> br.readLine() + br.readLine());

完整代碼如下:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class test{
    @FunctionalInterface
    public interface BufferedReaderProcessor{
        String process(BufferedReader b) throws IOException;
    }
    public static String processFile(BufferedReaderProcessor p) throws IOException {
        try (BufferedReader br =
                     new BufferedReader(new FileReader("c:/tmp/data.txt"))){
            return p.process(br);
        }
    }
    public static void main(String[] args) throws IOException {
        String oneLine = processFile((BufferedReader br) -> br.readLine());
        String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
        System.out.println(oneLine);
        System.out.println(twoLines);
    }
}

現(xiàn)在已經(jīng)能成功的利用函數(shù)式接口來傳遞Lambda。

使用函數(shù)式接口

函數(shù)接口定義且只定義了一個(gè)抽象方法植影。函數(shù)式接口的抽象方法的簽名稱為函數(shù)描述符裳擎。因此,為了應(yīng)用不同的Lambda表達(dá)式思币,需要一套能夠描述常見函數(shù)描述符的函數(shù)式接口鹿响。
Java 8在java.util.function包中加入了許多新的函數(shù)式接口羡微,你可以重用它來傳遞多個(gè)不同的Lambda。

Predicate

java.util.function.Predicate<T>接口定義了一個(gè)名叫test的抽象方法惶我,它接受泛型T對(duì)象妈倔,并返回一個(gè)boolean。在需要表示一個(gè)涉及類型T的布爾表達(dá)式時(shí)绸贡,就可以使用這個(gè)接口盯蝴。

public interface Predicate<T> {
    boolean test(T t);
}

例如:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> p){
    List<T> results = new ArrayList<>();
    for (T s: list){
        if(p.test(s)){
            results.add(s);
        }    
    }
    return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
Consumer

java.util.function.Consumer<T>定義了一個(gè)名叫accept的抽象方法,它接受泛型T對(duì)象听怕,沒有返回捧挺。
例如:

@FunctionalInterface
public interface Comsumer<T> {
    void accept(T t);
}

public static <T> void forEach(List<T> list, Predicate<T> c){
    for (T i: list){
        c.accept(i);
    }
}
forEach(
      Arrays.asList(1,2,3,4,5),
      (Interger i) -> System.ou.println(i)
    );
Function

java.util.function.Function<T, R>定義了一個(gè)名叫apply的抽象方法,它接受一個(gè)泛型T對(duì)象尿瞭,并返回一個(gè)泛型R的對(duì)象闽烙。
下面將創(chuàng)建一個(gè)map方法,以將一個(gè)String列表映射到包含每個(gè)String長度的Interger列表:

@FunctionalInterface
public interface Function<T, R> {
    R accept(T t);
}

public static <T, R> List<R> map(List<T> list, Function<T, R> f){
    List<R> result = new ArrayList<>();
    for (T s: list){
        result.add(f.apply(s));
    }
    return result;
}
List<Integer> l =map(Arrays.asList("lambdas","in","action"), (String s) -> s.length());
原始類型特化

Java的類型有兩種: 引用類型原始類型 筷厘。但是泛型只能綁定到引用類型鸣峭。這是由于泛型內(nèi)部的實(shí)現(xiàn)方式造成的宏所。因此Java里有一個(gè)將原始類型轉(zhuǎn)換為對(duì)應(yīng)的引用類型的機(jī)制酥艳。這個(gè)機(jī)制叫裝箱(boxing)。相反的操作便叫拆箱(unboxing)爬骤。裝箱和拆箱操作是可以由自動(dòng)裝箱機(jī)制來自動(dòng)完成的充石。但是這在性能方面要付出代價(jià),裝箱后的值需要更多的內(nèi)存并且需要額外的內(nèi)存霞玄。

Java 8為原始類型帶來了一個(gè)專門的版本骤铃,用于在輸入和輸出都是原始類型時(shí)避免自動(dòng)裝箱的操作:

public interface IntPredicate{
    boolean test(int t);
}

無裝箱:

IntPredicate evenNumbers = (int i) -> i%2 ==0;
evenNumbers.test(1000);

裝箱:

Predicate<Integer> oddNumbers = (Integer i) -> i%2 == 1;
oddNumbers.test(1000);

類型檢查、類型推斷以及限制

Lambda本身并不包含它在實(shí)現(xiàn)哪個(gè)函數(shù)式接口的信息坷剧。為了全面了解Lambda表達(dá)式應(yīng)該知道Lambda的實(shí)際類型是什么惰爬。

類型檢查

Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(例如接受它傳遞的方法的參數(shù)惫企,或接受它的值的局部變量)中Lambda表達(dá)式需要的類型稱為目標(biāo)類型撕瞧。

同樣的Lambda,不同的函數(shù)式接口

有了目標(biāo)類型的概念狞尔,同一個(gè)Lambda表達(dá)式就可以與不同的函數(shù)式接口聯(lián)系起來丛版。

類型推斷

可以進(jìn)一步簡化你的代碼。編譯器會(huì)從上下文(目標(biāo)類型)推斷出痛什么函數(shù)式接口來配合Lambda表達(dá)式偏序,這意味著它也可以推斷出適合Lambda的簽名页畦,這樣就可以再Lambda語法中省去標(biāo)注參數(shù)類型。

沒有自動(dòng)類型推斷:

Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

有自動(dòng)類型推斷:

Comparator<Apple> c =(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
使用局部變量

之前介紹的所有Lambda表達(dá)式都只用到了其主體里面的參數(shù)研儒。但Lambda表達(dá)式也允許使用自由變量(不是參數(shù)豫缨,而是在外層作用域中定義的變量)独令,就像匿名類一樣。它們被稱作捕獲Lambda州胳。

例如:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);

Lambda捕獲了portNumber變量记焊。但關(guān)于對(duì)這些變量可以做什么有一些限制。Lambda可以沒有限制的捕獲實(shí)例變量和靜態(tài)變量(也就是在其主體中引用)栓撞。但是局部變量必須顯示聲明為final(或事實(shí)上是final)遍膜。
下面的代碼無法編譯,因?yàn)閜ortNumber變量被賦值了兩次:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;

實(shí)例變量和局部變量背后的實(shí)現(xiàn)有一個(gè)關(guān)鍵的不同瓤湘。實(shí)例變量都存儲(chǔ)在堆中瓢颅,而局部變量則保存在棧上。Java在訪問自由局部變量時(shí)弛说,實(shí)際上是在訪問它的副本挽懦,而不是訪問原始變量。如果Lambda可以直接訪問局部變量木人,而且Lambda是在一個(gè)線程中使用的信柿,則使用Lambda的線程,可能會(huì)在分配該變量的線程將這個(gè)變量收回之后去訪問該變量醒第,這回引發(fā)造成線程不安全的新的可能性渔嚷。

方法引用

方法引用可以重復(fù)使用現(xiàn)有的方法定義,并像Lambda一樣傳遞它們稠曼。

下面是用方法引用寫的一個(gè)排序的例子:

先前:

invenstory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

之后(使用方法引用和 java.util.Comparator.comparing ):

inventory.sort(comparing(Apple::getWeight));

方法引用可以被看做僅僅調(diào)用特定方法的Lambda的一種快捷寫法形病。它的基本思想是,如果一個(gè)Lambda代表的只是“直接調(diào)用這個(gè)方法”霞幅,那最好還是用名稱來調(diào)用它漠吻,而不是去描述如何調(diào)用它。

方法引用就是讓你根據(jù)已有的方法實(shí)現(xiàn)來創(chuàng)建Lambda表達(dá)式司恳。

當(dāng)你需要使用方法引用時(shí)途乃,目標(biāo)引用放在分隔符 :: 前,方法的名稱放在后面扔傅。例如 Apple::getWeight 就是引用了Apple類中定義的方法getWeight耍共。

方法引用也可以看做針對(duì)僅僅設(shè)計(jì)單一方法的Lambda的語法糖

構(gòu)建方法引用

方法引用主要有三類铅鲤。

  1. 指向靜態(tài)方法的方法引用(Integer::parseInt)
  2. 指向任意類型實(shí)例方法的方法引用(String::length)
  3. 指向現(xiàn)有對(duì)象的實(shí)例方法的方法引用(Transaction::getValue)

第二種方法引用的思想就是你在引用一個(gè)對(duì)象的方法划提,而這個(gè)對(duì)象本身是Lambda的一個(gè)參數(shù)。
例如 (String s) -> s.toUpperCase() 可以寫作 String::toUpperCase邢享。

第三種方法引用指的是鹏往,你在Lambda中調(diào)用一個(gè)已經(jīng)存在的外部對(duì)象的方法。例如,Lambda表達(dá)式 () -> expenssiveTransaction.getValue() 可以寫作 expensiveTransaction::getValue伊履。

方法引用不需要括號(hào)韩容,是因?yàn)闆]有實(shí)際調(diào)用這個(gè)方法。

構(gòu)造函數(shù)引用

可以利用現(xiàn)有構(gòu)造函數(shù)的名稱和關(guān)鍵字來創(chuàng)建它的一個(gè)引用 ClassName:new

例如:

List<Integer> weight = Arrays.asList(7,3,4,10);
List<Apple> apples = map(weights, Apple::new);
public static List<Apple> map(List<Integer> List, Function<Integer, Apple> f){
    List<Apple> result = new ArrayList<>();
    for(Integer e: list){
        result.add(f.apply(e))
    }
    return result;
}

Lambda和方法引用實(shí)戰(zhàn)(用不同的排序策略給一個(gè)Apple列表排序)

第1步:傳遞代碼

Java API已經(jīng)提供了一個(gè)List可用的sort方法唐瀑,要如何把排序策略傳遞給sort呢群凶?sort方法的簽名樣子如下:

void sort(Comparator<? super E> c)

它需要一個(gè)Comparator對(duì)象來比較兩個(gè)Apple。

第一個(gè)解決方案看上去是這樣的:

public class AppleComparator implements Comparator<Apple>{
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
}

inventory.sort(new AppleComparator());
第2步:使用匿名類改進(jìn)
inventory.sort(new AppleComparator<Apple>(){
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

使用匿名類的意義僅僅在于不用為了只實(shí)例化一次而實(shí)現(xiàn)一個(gè)Comparator哄辣。

第3步:使用Lambda表達(dá)式
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
函數(shù)復(fù)合

還可以把Function接口所代表的Lambda表達(dá)式復(fù)合起來请梢。Function接口有兩個(gè)默認(rèn)方法:andThen和 compose。它們都會(huì)返回Function的一個(gè)實(shí)例力穗。

  1. andThen 方法會(huì)返回一個(gè)函數(shù)毅弧,它先對(duì)輸入應(yīng)用一個(gè)給定函數(shù)沫浆,再對(duì)輸出應(yīng)用另一個(gè)函數(shù)辆毡。

比如函數(shù)f給數(shù)字加1匾乓,另一個(gè)函數(shù)給數(shù)字乘2:

Function<Integer,Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> x * 2;
Function<Integer,Integer> h = f.andThen(g);
int result = h.apply(1);

在數(shù)學(xué)上意味著g(f(x))篙议。

  1. compose 方法先把給定的函數(shù)用作compose的參數(shù)里面給的那個(gè)函數(shù),然后再把函數(shù)本身用于結(jié)果屎勘。
Function<Integer,Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> x * 2;
Function<Integer,Integer> h = f.compose(g);
int result = h.apply(1);

在數(shù)學(xué)上意味著f(g(x))示启。

第四章

引入流

集合是Java中使用最多的API湃密。幾乎每個(gè)Java應(yīng)用程序都會(huì)制造和處理集合巫员。但集合的操作卻遠(yuǎn)遠(yuǎn)算不上完美庶香。

流是Java API,它允許你以聲明性方式處理數(shù)據(jù)集合疏遏。此外流還可以透明性地并行處理脉课,無需寫任何多線程代碼救军。

下面是一個(gè)Java 7實(shí)現(xiàn)的 返回低熱量的菜肴名稱并按照卡路里排序:

List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish d: dishes){
    if(d.getCalories() < 400){
        lowCaloricDishes.add(d);
    }
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
    public int compare(Dish d1, Dish d2){
        return Integer.compare(d1.getCalories(), d2.getCalories());
    }
});
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d: lowCaloricDishes){
    lowCaloricDishesName.add(d.getName());
}

變量lowCaloricDishes唯一的作用就是作為一次性的中間容器财异。

下面是Java 8實(shí)現(xiàn):

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName = 
    menu.stream()
        .filter(d -> d.getCalories() < 400)
        .sorted(comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(toList());

為了利用多核架構(gòu)并行執(zhí)行這段代碼,只需要把 stream() 換成 parallelStream()

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName = 
    menu.parallelStream()
        .filter(d -> d.getCalories() < 400)
        .sorted(comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(toList());

使用新的方法 有幾個(gè)顯而易見的好處:

  • 代碼時(shí)以聲明性方式寫的
  • 通過把幾個(gè)基礎(chǔ)操作鏈接起來唱遭,來表達(dá)復(fù)雜的數(shù)據(jù)處理流水線戳寸,同時(shí)保持代碼清晰可讀。

使用Java 8 的Stream API的優(yōu)點(diǎn):

  • 聲明性
  • 可復(fù)合
  • 可并行

流簡介

流是什么拷泽?簡短的定義就是“從支持?jǐn)?shù)據(jù)處理操作的源 生成的 元素序列”疫鹊。

  • 元素序列 ----- 就像集合一樣,流也提供一個(gè)接口司致,可以訪問特定元素類型的一組有序值拆吆。
  • 源 ----- 流會(huì)使用一個(gè)提供數(shù)據(jù)的源,如集合脂矫、數(shù)組或輸入/輸出資源枣耀。
  • 數(shù)據(jù)處理操作 ----- 流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫的操作。以及函數(shù)式編程語言中的常用操作庭再。

此外捞奕,流操作有兩個(gè)重要的特點(diǎn):

  • 流水線 ----- 很多流操作本身會(huì)返回一個(gè)流牺堰,這樣多個(gè)操作就可以連接起來,形成一個(gè)大的流水線颅围。
  • 內(nèi)部迭代 ----- 與使用迭代器顯式迭代的集合不同伟葫,流的迭代操作是在背后進(jìn)行的。

例如:

import static java.util.stream.Collectors.toList;
List<String> threeHighCaloricDishesName = 
    menu.stream()
        .filter(d -> d.getCalories() > 300)
        .map(Dish::getName)
        .limit(3)
        .collect(toList());
    System.out.println(threeHighCaloricDishNames);

在上面示例代碼中院促,先是對(duì)menu調(diào)用stream方法筏养,由菜單得到一個(gè)流。數(shù)據(jù)源是menu常拓,它給流提供一個(gè)元素序列撼玄。接下來,對(duì)流應(yīng)用一系列數(shù)據(jù)處理操作:filter墩邀、map掌猛、limit和collect。除了collect之外眉睹,所有這些操作都會(huì)返回一個(gè)流荔茬,這樣就可以連接成一條流水線。最后竹海,collect操作開始處理流水線慕蔚,并返回結(jié)果(它和別的操作不一樣,因?yàn)樗祷氐氖且粋€(gè)List)斋配。

在調(diào)用collect之前孔飒,沒有任何結(jié)果產(chǎn)生,實(shí)際上根本就沒有從menu里選擇元素艰争,可以理解為:鏈中的方法調(diào)用都在排隊(duì)等待坏瞄,直到調(diào)用collect。

  • filter ----- 接受Lambda甩卓,從流中排除某些元素鸠匀。
  • map ----- 接受一個(gè)Lambda,將元素轉(zhuǎn)換成其他形式或提取信息逾柿。
  • limit ----- 截?cái)嗔髯汗鳎蛊湓夭怀^給定數(shù)量。
  • collect ----- 將流轉(zhuǎn)換為其他形式机错。

這樣做的好處在于爬范,你并沒有去實(shí)現(xiàn)篩選、提取或截?cái)喙δ苋醴耍琒tream庫已經(jīng)自帶了青瀑。

流與集合

粗略的說,集合與流之間的差異就在于什么時(shí)候進(jìn)行計(jì)算。集合是一個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)狱窘,它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值杜顺,集合中每個(gè)元素都得算出來來才能添加到集合中(不管往集合里加?xùn)|西或者刪東西,集合中的每個(gè)元素都是放在內(nèi)存里的蘸炸,元素都得先算出來才能成為集合的一部分)躬络。
流則是概念上固定的數(shù)據(jù)結(jié)構(gòu),其元素是按需計(jì)算的搭儒。從另一個(gè)角度來說穷当,流就像是一個(gè)延遲創(chuàng)建的集合:只有在消費(fèi)者要求的時(shí)候才會(huì)計(jì)算值。而集合則是急切創(chuàng)建的淹禾。

只能遍歷一次

和迭代器類似馁菜,流只能遍歷一次。遍歷完以后铃岔,這個(gè)流就已經(jīng)被消費(fèi)掉了汪疮。可以從原始數(shù)據(jù)源那里再獲得一個(gè)新的流來重新遍歷一遍毁习。

以下代碼會(huì)拋出一個(gè)異常智嚷,提示流已被消費(fèi)掉了:

List<String> title = Arrays.asList("Java8","In","Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
外部迭代和內(nèi)部迭代

集合和流的另一個(gè)關(guān)鍵區(qū)別在于它們遍歷數(shù)據(jù)的方式。

使用Collection接口需要用戶去做迭代(比如用for-each)纺且,這稱為外部迭代盏道。而Stream庫使用內(nèi)部迭代 ----- 它幫你把迭代做了,還把得到的流值存在了某個(gè)地方载碌,只要給出一個(gè)函數(shù)說要干什么就可以了猜嘱。

用for-each循環(huán)外部迭代:

List<String> names = new ArrayList<>();
for(Dish d: menu){
    names.add(d.getName());
}

用背后的迭代器做外部迭代:

List<String> names = new ArrayList<>();
Iterator<String> iterator =menu.iterator();
while(iterator.hasNext()) {
    Dish d = iterator.next();
    names.add(d.getName());
}

流:內(nèi)部迭代:

List <String> names = menu.stream()
           .map(Dish::getName)
           .collect(toList());

流操作

java.util.stream.Stream中的stream接口定義了許多操作。它們可以被分為兩大類:中間操作終端操作嫁艇±柿妫可以被連接起來的流操作稱為中間操作,關(guān)閉流的操作稱為終端操作裳仆。

中間操作

中間操作會(huì)返回另一個(gè)流腕让。這讓多個(gè)操作可以連接起來形成一個(gè)查詢孤钦。更重要的是歧斟,除非流水線上觸發(fā)一個(gè)終端操作,否則中間操作不會(huì)執(zhí)行任何處理偏形。中間操作會(huì)合并起來在終端操作時(shí)一次性全部處理静袖。

終端操作

終端操作會(huì)從流的流水線生成結(jié)果。其結(jié)果可以是任何不是流的值俊扭。

使用流

流的使用一般包括三件事:

  • 一個(gè)數(shù)據(jù)源來執(zhí)行一個(gè)查詢队橙;
  • 一個(gè)中間操作鏈,形成一條流的流水線;
  • 一個(gè)終端操作捐康,執(zhí)行流水線并生成結(jié)果仇矾。

流的流水線背后的理念類似于構(gòu)建器模式。在構(gòu)建器模式中有一個(gè)調(diào)用鏈用來設(shè)置一套配置(流的中間操作鏈)解总,接著是調(diào)用built方法(流的終端操作)贮匕。


第五章

篩選和切片

用謂詞篩選

Stream接口支持filter方法。該操作會(huì)接受一個(gè)謂詞作為參數(shù)花枫,并返回一個(gè)包括所有符合謂詞的元素的流刻盐。

例如:

List<Dish> vegetarianMenu = menu.stream()
                                .filter(Dish::isVegetarian)
                                .collect(toList());
篩選各異的元素

流支持distinct方法,它會(huì)返回一個(gè)元素各異的流劳翰。例如敦锌,以下代碼會(huì)篩選出列表中所有的偶數(shù),并確保沒有重復(fù)佳簸。

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream()
        .filter(i -> i%2 ==0)
        .distinct()
        .forEach(System.out::println);
截短流

流支持limit(n)方法乙墙,該方法會(huì)返回一個(gè)不超過給定長度n的流。

例如:

List<Dish> dishes = menu.stream()
            .filter(d -> d.getCalories() > 300)
            .limit(3)
            collect(toList());
跳過元素

流還支持skip(n)方法生均,返回一個(gè)扔掉前n個(gè)元素的流伶丐。如果流元素不足n個(gè)則返回一個(gè)空流。

例如:

List<Dish> dishes = menu.stream()
            .filter(d -> d.getCalories() > 300)
            .skip(2)
            collect(toList());

映射

一個(gè)常見的數(shù)據(jù)處理套路就是從某些對(duì)象中選擇信息疯特。在SQL里哗魂,可以從表中選擇一列。Stream API也通過map和flatMap方法提供了類似的工具漓雅。

對(duì)流中每一個(gè)元素應(yīng)用函數(shù)

流支持map方法录别,它會(huì)接受一個(gè)函數(shù)作為參數(shù)。這個(gè)函數(shù)會(huì)被應(yīng)用到每個(gè)元素上邻吞,并將其映射成一個(gè)新的元素组题。

例如:

//提取菜肴名稱
List<String> dishNames = menu.stream()
            .map(Dish::getName)
            .collect(toList());
流的扁平化

對(duì)于一個(gè)單詞表,如何返回一張列表抱冷,列出里面各不相同的字符崔列?例如給定單詞列表["Hello","World"],要返回列表["H","e","l","o","W","r","d"]旺遮。

最開始的版本可能是這樣的:

words.stream()
    .map(word -> word.split(""))
    .collect(toList());

這樣做的問題在于赵讯,傳遞給map方法的Lambda為每個(gè)單詞返回了一個(gè)String[]。因此map返回的流實(shí)際上是Stream<String[]>類型的耿眉。而真正想要的是用Stream<String>來表示一個(gè)字符流边翼。

  1. 嘗試使用map和Arrays.stream()

首先,需要一個(gè)字符流鸣剪,而不是數(shù)組流组底。有一個(gè)叫做Arrays.stream()的方法可以接受一個(gè)數(shù)組并產(chǎn)生一個(gè)流丈积,例如

String[] arraysOfWords = {Goodbye", "World"};
Stream<String>  streamOfWords = Arrays.stream(arrayOfWords); 

將它用在前面的流水線里

words.stream()
    .map(word -> word.split(""))
    .map(Arrays::stream)
    .distinct()
    .collect(toList());

當(dāng)前的解決方案仍然搞不定,因?yàn)楝F(xiàn)在得到的是一個(gè)流的列表(因?yàn)橄仁前衙總€(gè)單詞轉(zhuǎn)換成一個(gè)字母數(shù)組债鸡,然后把每個(gè)數(shù)組變成了獨(dú)立的流

2.使用flatMap

可以像下面這樣使用flatMap來解決這個(gè)問題:

List<String> uniqueCharacters =  
        words.stream()
            .map(word -> word.split(""))
            .map(Arrays::stream)
            .distinct()
            .collect(toList());

使用flatMap方法的效果是江滨,各個(gè)數(shù)組并不是分別映射成一個(gè)流,而是映射成流的內(nèi)容厌均。所有使用map()時(shí)生成的單個(gè)流都被合并起來牙寞,即扁平化為一個(gè)流。
簡而言之flatMap方法讓你把一個(gè)流中的每個(gè)值都換成另一個(gè)流莫秆,然后把所有的流連接起來稱為一個(gè)流间雀。

測驗(yàn):

1.給定兩個(gè)數(shù)字列表[1,2,3]和[3,4],返回綜合能被3整除的數(shù)對(duì)镊屎。

List<Integer> numbers1 = Arrays.asList(1,2,3);
List<Integer> numbers2 = Arrays.asList(3,4);
List<int[]> pairs = numbers1.stream()
        .flatmap(i -> numbers2.stream()
                .filter( j -> (j+i) % 3 == 0)
                .map( j -> new int[]{i,j})
        )
        .collect(toList());

查找和匹配

另一個(gè)常見的數(shù)據(jù)處理套路是看數(shù)據(jù)集中某些元素是否匹配一個(gè)給定的屬性惹挟。Stream API通過allMatch、anyMatch缝驳、noneMatch连锯、findFirst和findAny方法提供這樣的工具。

檢查謂詞是否至少匹配一個(gè)元素(anyMatch)
if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("The menu is vegetarian friendly");
}

anyMatch方法返回一個(gè)boolean值用狱,因此是一個(gè)終端操作

檢查謂詞是否匹配所有元素(allMatch)
menu.stream().allMatch( d -> d.getCalories() < 1000);
檢查謂詞是否與所有謂詞都不匹配(noneMatch)
menu.stream().noneMatch( d -> d.getCalories() >= 1000);
查找元素

findAny方法將返回當(dāng)前流的任意元素:

Optional<Dish> dish = 
        menu.stream()
                .filter(Dish::isVegetarian)
                .findAny();

但代碼里的Optional是什么运怖?

Optional簡介

Optional<T>類(java.util.Optional)是一個(gè)容器類,代表一個(gè)值存在或不存在夏伊。Java 8通過引入Optional<T> 來避免返回眾所周知的容易出問題的null摇展。

Optional里有幾種 可以迫使你顯式地檢查值是否存在或處理值不存在情形的 方法。

  • isPresent()將在Optional包含值的時(shí)候返回true溺忧,否則返回false
  • isPresent(Consumer<T> block) 會(huì)在值存在的時(shí)候執(zhí)行給定的代碼塊咏连。
  • T get() 會(huì)在值存在時(shí)返回值,否則拋出一個(gè)NoSuchElement異常鲁森。
  • T orElse(T other)會(huì)在值存在時(shí)返回值,否則返回一個(gè)默認(rèn)值歌溉。

例如在前面的findAny代碼中你需要顯式地檢查Optional對(duì)象中是否存在一道菜可以訪問其名稱:

menu.stream()
        .filter(Dish::isVegetarian)
        .findAny()
        .ifPresent(d -> System.out.println(d.getName());
查找一個(gè)元素

有些流有一個(gè)出現(xiàn)順序來指定流中項(xiàng)目出現(xiàn)的邏輯順序。對(duì)于這種流草慧,想要找到第一個(gè)元素。為此有一個(gè)findFirst方法榜晦,它的工作方式類似于findAny冠蒋,它們的區(qū)別在于并行上的限制乾胶,如果不關(guān)心返回的元素是哪個(gè),就用findAny识窿。


歸約

如何把一個(gè)流中的元素組合起來并表達(dá)更復(fù)雜的查詢斩郎?如“計(jì)算菜單中的總卡路里”或“菜單中卡路里最高的菜是哪一個(gè)”。此類查詢需要將流中所有元素結(jié)合起來缩宜,得到一個(gè)值,比如一個(gè)Integer锻煌。這樣的查詢可以被歸類為歸約操作姻蚓。

元素求和

先看看如何使用for-each循環(huán)來對(duì)數(shù)字列表中的元素求和:

int sum = 0;
for(int x: numbers){
    sum += x;
}

這段代碼中有兩個(gè)參數(shù):

  • 總和變量的初始值
  • 將列表中所有元素結(jié)合在一起的操作

reduce對(duì)上面這種重復(fù)應(yīng)用的模式做了抽象宋梧,可以像下面這樣對(duì)流中所有的元素求和:

int sum = numbers.stream().reduce(0,(a, b) -> a+b);

reduce接受兩個(gè)參數(shù):

  • 一個(gè)初始值
  • 一個(gè)BinaryOperator<T>來將兩個(gè)元素結(jié)合起來產(chǎn)生一個(gè)新值狰挡。

還可以使用方法引用讓這段代碼更簡潔。在Java 8中加叁,Integer類現(xiàn)在有了 一個(gè)靜態(tài)的sum方法來對(duì)兩個(gè)數(shù)求和:

int sum = numbers.stream().reduce(0, Integer::sum);

無初始值

reduce還有一個(gè)重載的變體,它不接受初始值它匕,但是會(huì)返回一個(gè)Optional對(duì)象(考慮到流中沒有任何元素,無法返回其和的情況):

Optional<Integer> sum = numbers.stream().reduce((a, b) -> a+b);
最大值和最小值

利用reduce來計(jì)算最大值和最小值

Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);

數(shù)值流

Stream API還提供了原始類型流特化愈污,專門支持處理數(shù)值流的方法

原始類型流特化

Java 8引入了三個(gè)原始類型特化流來解決這個(gè)問題:IntStream、DoubleStream和LongStream暂雹,分別將流中元素特化為int创夜、long和double杭跪,可以避免暗含的裝箱成本驰吓。每個(gè)接口都帶來了進(jìn)行常用數(shù)值歸約的新方法,比如對(duì)數(shù)值流求和的sum檬贰,找到最大元素的max。

  1. 映射到數(shù)值流

將流轉(zhuǎn)換為特化版本的常用方法是mapToInt翁涤、mapToDouble和mapLong萌踱。這些方法和前面的map方法的工作方式一樣号阿,只不過返回的是一個(gè)特化流并鸵,而不是Stream<T>扔涧。例如可以像下面這一行用mapToInt對(duì)menu中的卡路里求和:

int calories = menu.stream()
                .mapToInt(Dish::getCalories)
                .sum();

mapToInt會(huì)返回一個(gè)IntStream,然后就可以調(diào)用IntStream接口中定義的sum方法對(duì)卡路里求和枯夜。如果流是空的,sum默認(rèn)返回0湖雹。
IntStream還支持其他的方法,如max劝枣、min、average等舔腾。

  1. 轉(zhuǎn)換回對(duì)象流

要把原始流轉(zhuǎn)換成一般流,可以使用boxed方法稳诚,如下所示:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
  1. 默認(rèn)值OptionalInt

求和很容易,因?yàn)樗幸粋€(gè)默認(rèn)值0才避。但是如果要計(jì)算IntStream的最大元素,默認(rèn)值0是錯(cuò)誤的結(jié)果桑逝。如何區(qū)分沒有元素的流和最大值真的是0的流?Optional可以用Integer楞遏、String等參考類型來參數(shù)化。對(duì)于三種原始流特化首昔,也分別有一個(gè)Optional原始類型特化版本:OptionalInt、OptionalDouble和OptionalLong勒奇。

例如要找到IntStream中的最大元素,可以調(diào)用max方法赊颠,它會(huì)返回一個(gè)OptionalInt劈彪,如果沒有最大值的話蟋定,就可以顯示處理OptionalInt去定義一個(gè)默認(rèn)值了:

OptionalInt maxCalories = menu.stream()\
                .mapToInt(Dish::getCalories)
                .max();

int max = maxCalories.orElse(1);
數(shù)值范圍

比如生成1到100之間的所有數(shù)字草添。Java 8引入了兩個(gè)可以用于IntStream和LongStream的靜態(tài)方法驶兜,幫助生成這種范圍:range和rangeClosed远寸。這兩個(gè)方法都是第一個(gè)參數(shù)接受起始值,第二個(gè)參數(shù)接受結(jié)束值驰后。但range生成的范圍不包含結(jié)束值,而rangeClosed包含結(jié)束值灶芝。

IntStream evenNumbers = IntStream.rangeClosed(1, 100)
                              .filter(n -> n%2 == 0);

System.out.println(evenNumbers.count());

構(gòu)建流

接下來將介紹如何從值序列、數(shù)組夜涕、文件來創(chuàng)建流,甚至由生成函數(shù)來創(chuàng)建無限流女器。

由值創(chuàng)建流

使用靜態(tài)方法Stream.of通過顯式值創(chuàng)建一個(gè)流。它可以接受任意數(shù)量的參數(shù)驾胆。

例如:

Stream<String> stream  = stream.of("Java 8","In","Action");

可以使用empty得到一個(gè)空流:

Stream<String> emptyStream  = stream.empty();
由數(shù)組創(chuàng)建流

可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個(gè)流。它接受一個(gè)數(shù)組作為參數(shù)入桂。

int[] numbers = {2,3,5,7,11,13};
int sum = Arrays.stream(numbers).sum();
由文件生成流

java.nio.file.Files中的很多靜態(tài)方法都會(huì)返回一個(gè)流驳阎。例如抗愁,F(xiàn)iles.lines方法會(huì)返回一個(gè)由指定文件中的各行構(gòu)成的字符串流搞隐。

統(tǒng)計(jì)一個(gè)文件中有多少各不相同的詞:

long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"),charset.defaultCharset())){
        uniqueWords = lines.flatMap(line -> Arrays.stream(line.split("")))
                          .distinct()
                          .count();
}
catch(IOException e){
}

上面的代碼使用Files.lines得到一個(gè)流驹愚,其中的每個(gè)元素都是文件中的一行。然后對(duì)line調(diào)用split方法將行拆分成單詞劣纲。最后把distinct和count方法鏈接起來逢捺,統(tǒng)計(jì)出各不相同的單詞的個(gè)數(shù)。

由函數(shù)生成流

Stream API提供了兩個(gè)靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate癞季。這兩個(gè)操作可以創(chuàng)建所謂的無限流(不像從固定集合創(chuàng)建的流那樣有固定大小的流)劫瞳。由iterate和generate產(chǎn)生的流會(huì)用給定的函數(shù)按需創(chuàng)建值,因此可以無窮無盡地計(jì)算下去志于。一般來說應(yīng)該使用limit(n)對(duì)無限流加以限制,避免打印無窮多個(gè)值养泡。

  1. 迭代
Stream.iterate(0, n -> n+2)
        .limit(10)
        .forEach(System,out::println);

iterate接受一個(gè)初始值奈应。還有一個(gè)一次應(yīng)用在每個(gè)產(chǎn)生的新值上的Lambda(UnaryOperator<T>類型)澜掩。此操作將生成一個(gè)無限流 ----- 這個(gè)流沒有結(jié)尾杖挣,因?yàn)橹凳前葱栌?jì)算的肩榕,因此這個(gè)流是無界的惩妇。

  1. 生成

與iterate方法不同的是,generate方法不是依次對(duì)每個(gè)新生成的值應(yīng)用函數(shù)的乔妈。它接受一個(gè)Supplier<T>類型的Lambda提供新的值挺份。

Stream.generate(Math:random)
            .limit(5)
            .forEach(System.out::println)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市匀泊,隨后出現(xiàn)的幾起案子优训,更是在濱河造成了極大的恐慌各聘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躲因,死亡現(xiàn)場離奇詭異,居然都是意外死亡搞监,警方通過查閱死者的電腦和手機(jī)镰矿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绝淡,“玉大人,你說我怎么就攤上這事牢酵。” “怎么了布近?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵潘拨,是天一觀的道長饶号。 經(jīng)常有香客問我,道長琅束,這世上最難降的妖魔是什么算谈? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮艾船,結(jié)果婚禮上高每,老公的妹妹穿的比我還像新娘。我一直安慰自己鲸匿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布运授。 她就那樣靜靜地躺著乔煞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渡贾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天锦溪,我揣著相機(jī)與錄音,去河邊找鬼防楷。 笑死则涯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粟判。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼角钩,長吁一口氣:“原來是場噩夢啊……” “哼呻澜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脊髓,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤栅受,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屏镊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涛贯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蔚出,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稀余。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趋翻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师骗,到底是詐尸還是另有隱情,我是刑警寧澤辟癌,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布黍少,位于F島的核電站,受9級(jí)特大地震影響厂置,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜智绸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一砸紊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧醉顽,春花似錦平挑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赏枚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凡辱,已是汗流浹背栗恩。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乳乌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓汉操,卻偏偏與公主長得像,于是被迫代替她去往敵國和親其弊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膀斋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)糊识,斷路器摔蓝,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,233評(píng)論 1 2
  • 本文是對(duì) Brian Goetz 的 State of Lambda 一文的翻譯 為什么要翻譯這個(gè)系列? andr...
    aaron688閱讀 3,888評(píng)論 4 31
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,579評(píng)論 1 118
  • 有時(shí)人自己講述曾經(jīng)發(fā)生在自個(gè)身上實(shí)實(shí)在在的真實(shí)的事情斜姥。講出來自己會(huì)跟隨曾經(jīng)的畫面沧竟,滿臉蕩漾著美好和幸福的模樣...
    燦爛陽光下閱讀 271評(píng)論 0 0