成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Java? 教程(Lambda表達(dá)式)

lansheng228 / 2605人閱讀

Lambda表達(dá)式

匿名類(lèi)的一個(gè)問(wèn)題是,如果匿名類(lèi)的實(shí)現(xiàn)非常簡(jiǎn)單,例如只包含一個(gè)方法的接口,那么匿名類(lèi)的語(yǔ)法可能看起來(lái)不實(shí)用且不清楚,在這些情況下,你通常會(huì)嘗試將功能作為參數(shù)傳遞給另一個(gè)方法,例如當(dāng)有人單擊按鈕時(shí)應(yīng)采取的操作,Lambda表達(dá)式使你可以執(zhí)行此操作,將功能視為方法參數(shù),或?qū)⒋a視為數(shù)據(jù)。

上一節(jié)匿名類(lèi)向你展示了如何在不給它命名的情況下實(shí)現(xiàn)基類(lèi),雖然這通常比命名類(lèi)更簡(jiǎn)潔,但對(duì)于只有一個(gè)方法的類(lèi),即使是匿名類(lèi)也似乎有點(diǎn)過(guò)多和繁瑣,Lambda表達(dá)式允許你更緊湊地表達(dá)單方法類(lèi)的實(shí)例。

Lambda表達(dá)式的理想用例

假設(shè)你正在創(chuàng)建社交網(wǎng)絡(luò)應(yīng)用程序,你希望創(chuàng)建一項(xiàng)功能,使管理員能夠?qū)M足特定條件的社交網(wǎng)絡(luò)應(yīng)用程序成員執(zhí)行任何類(lèi)型的操作,例如發(fā)送消息,下表詳細(xì)描述了此用例:

字段 描述
名稱(chēng) 對(duì)選定的成員執(zhí)行操作
主要角色 管理員
前提條件 管理員已登錄系統(tǒng)
后置條件 僅對(duì)符合指定條件的成員執(zhí)行操作
主要成功案例 1. 管理員指定要執(zhí)行特定操作的成員的條件
2. 管理員指定要對(duì)這些選定成員執(zhí)行的操作
3. 管理員選擇Submit按鈕
4. 系統(tǒng)查找符合指定條件的所有成員
5. 系統(tǒng)對(duì)所有匹配成員執(zhí)行指定的操作
擴(kuò)展 管理員可以選擇在指定要執(zhí)行的操作之前或選擇Submit按鈕之前預(yù)覽符合指定條件的成員
發(fā)生頻率 一天中很多次

假設(shè)此社交網(wǎng)絡(luò)應(yīng)用程序的成員由以下Person類(lèi)表示:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }

    public void printPerson() {
        // ...
    }
}

假設(shè)你的社交網(wǎng)絡(luò)應(yīng)用程序的成員存儲(chǔ)在List實(shí)例中。

本節(jié)首先介紹這種用例的簡(jiǎn)單方法,它使用局部和匿名類(lèi)改進(jìn)了這種方法,然后使用lambda表達(dá)式以高效和簡(jiǎn)潔的方法完成,在示例RosterTest中找到本節(jié)中描述的代碼摘錄。

方法1:創(chuàng)建搜索匹配一個(gè)特征的成員的方法

一種簡(jiǎn)單的方法是創(chuàng)建幾種方法,每種方法都會(huì)搜索與一個(gè)特征匹配的成員,例如性別或年齡,以下方法打印超過(guò)指定年齡的成員:

public static void printPersonsOlderThan(List roster, int age) {
    for (Person p : roster) {
        if (p.getAge() >= age) {
            p.printPerson();
        }
    }
}

注意:List是有序集合,集合是將多個(gè)元素組合到一個(gè)單元中的對(duì)象,集合用于存儲(chǔ)、檢索、操作和傳遞聚合數(shù)據(jù),有關(guān)集合的更多信息,請(qǐng)參閱集合路徑。

這種方法可能會(huì)使你的應(yīng)用程序變得脆弱,這是由于引入了更新(例如更新的數(shù)據(jù)類(lèi)型)導(dǎo)致應(yīng)用程序無(wú)法工作的可能性。假設(shè)你升級(jí)應(yīng)用程序并更改Person類(lèi)的結(jié)構(gòu),使其包含不同的成員變量,也許類(lèi)使用不同的數(shù)據(jù)類(lèi)型或算法記錄和測(cè)量年齡,你必須重寫(xiě)大量API以適應(yīng)此更改,此外,這種方法是不必要的限制;例如,如果你想要打印年齡小于某個(gè)年齡的成員,該怎么辦?

方法2:創(chuàng)建更多廣義搜索方法

以下方法比printPersonsOlderThan更通用;它會(huì)在指定的年齡范圍內(nèi)打印成員:

public static void printPersonsWithinAgeRange(
    List roster, int low, int high) {
    for (Person p : roster) {
        if (low <= p.getAge() && p.getAge() < high) {
            p.printPerson();
        }
    }
}

如果你想要打印指定性別的成員,或指定性別和年齡范圍的組合,該怎么辦?如果你決定更改Person類(lèi)并添加其他屬性(如關(guān)系狀態(tài)或地理位置),該怎么辦?雖然此方法比printPersonsOlderThan更通用,但嘗試為每個(gè)可能的搜索查詢創(chuàng)建多帶帶的方法仍然會(huì)導(dǎo)致代碼脆弱,你可以改為分離指定要在其他類(lèi)中搜索的條件的代碼。

方法3:在局部類(lèi)中指定搜索條件代碼

以下方法打印與你指定的搜索條件匹配的成員:

public static void printPersons(
    List roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

此方法通過(guò)調(diào)用方法tester.test來(lái)檢查List參數(shù)名單中包含的每個(gè)Person實(shí)例是否滿足CheckPerson參數(shù)tester中指定的搜索條件,如果方法tester.test返回true值,則在Person實(shí)例上調(diào)用方法printPersons。

要指定搜索條件,請(qǐng)實(shí)現(xiàn)CheckPerson接口:

interface CheckPerson {
    boolean test(Person p);
}

以下類(lèi)通過(guò)指定方法test的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)CheckPerson接口,此方法可過(guò)濾符合美國(guó)選擇性服務(wù)條件的成員:如果Person參數(shù)為男性且年齡介于18和25之間,則返回true值:

class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

要使用此類(lèi),你需要?jiǎng)?chuàng)建它的新實(shí)例并調(diào)用printPersons方法:

printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

雖然這種方法不那么脆弱 — 如果更改Person的結(jié)構(gòu),則不必重寫(xiě)方法 — 你仍然需要額外的代碼:你計(jì)劃在應(yīng)用程序中執(zhí)行的每個(gè)搜索的新接口和局部類(lèi),因?yàn)?b>CheckPersonEligibleForSelectiveService實(shí)現(xiàn)了一個(gè)接口,所以你可以使用匿名類(lèi)而不是局部類(lèi),并且無(wú)需為每次搜索聲明一個(gè)新類(lèi)。

方法4:在匿名類(lèi)中指定搜索條件代碼

下面調(diào)用方法printPersons的一個(gè)參數(shù)是一個(gè)匿名類(lèi),它可過(guò)濾符合美國(guó)選擇性服務(wù)條件的成員:那些男性、年齡在18到25歲之間的人:

printPersons(
    roster,
    new CheckPerson() {
        public boolean test(Person p) {
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

此方法減少了所需的代碼量,因?yàn)槟悴槐貫橐獔?zhí)行的每個(gè)搜索創(chuàng)建新類(lèi),但是,考慮到CheckPerson接口只包含一個(gè)方法,匿名類(lèi)的語(yǔ)法很笨重,在這種情況下,你可以使用lambda表達(dá)式而不是匿名類(lèi),如下一節(jié)中所述。

方法5:使用Lambda表達(dá)式指定搜索條件代碼

CheckPerson接口是一個(gè)功能性接口,功能性接口是僅包含一個(gè)抽象方法的任何接口(功能性接口可能包含一個(gè)或多個(gè)默認(rèn)方法或靜態(tài)方法),由于功能性接口僅包含一個(gè)抽象方法,因此在實(shí)現(xiàn)該方法時(shí)可以省略該方法的名稱(chēng)。為此,不使用匿名類(lèi)表達(dá)式,而是使用lambda表達(dá)式,該表達(dá)式在以下方法調(diào)用中顯示:

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

有關(guān)如何定義lambda表達(dá)式的信息,請(qǐng)參見(jiàn)Lambda表達(dá)式的語(yǔ)法。

你可以使用標(biāo)準(zhǔn)功能性接口代替CheckPerson接口,從而進(jìn)一步減少所需的代碼量。

方法6:將標(biāo)準(zhǔn)功能性接口與Lambda表達(dá)式一起使用

重新考慮CheckPerson接口:

interface CheckPerson {
    boolean test(Person p);
}

這是一個(gè)非常簡(jiǎn)單的接口,它是一個(gè)功能性接口,因?yàn)樗话粋€(gè)抽象方法,此方法接受一個(gè)參數(shù)并返回一個(gè)布爾值,該方法非常簡(jiǎn)單,在你的應(yīng)用程序中定義一個(gè)方法可能不值得,因此,JDK定義了幾個(gè)標(biāo)準(zhǔn)的功能性接口,你可以在java.util.function包中找到它們。

例如,你可以使用Predicate接口代替CheckPerson,該接口包含方法boolean test(T t)

interface Predicate {
    boolean test(T t);
}

接口Predicate是泛型接口的示例(有關(guān)泛型的更多信息,請(qǐng)參閱泛型(更新)課程),泛型類(lèi)型(例如泛型接口)在尖括號(hào)(<>)中指定一個(gè)或多個(gè)類(lèi)型參數(shù),該接口僅包含一個(gè)類(lèi)型參數(shù)T。當(dāng)你使用實(shí)際類(lèi)型參數(shù)聲明或?qū)嵗盒皖?lèi)型時(shí),你具有參數(shù)化類(lèi)型,例如,參數(shù)化類(lèi)型Predicate如下:

interface Predicate {
    boolean test(Person t);
}

此參數(shù)化類(lèi)型包含一個(gè)方法,該方法具有與CheckPerson.boolean test(Person p)相同的返回類(lèi)型和參數(shù),因此,你可以使用Predicate代替CheckPerson,如下面的方法所示:

public static void printPersonsWithPredicate(
    List roster, Predicate tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

因此,以下方法調(diào)用與在方法3:在局部類(lèi)中指定搜索條件代碼以獲取有資格獲得選擇性服務(wù)的成員中調(diào)用printperson時(shí)相同:

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

這不是此方法中使用lambda表達(dá)式的唯一可能位置,以下方法提出了使用lambda表達(dá)式的其他方法。

方法7:在整個(gè)應(yīng)用程序中使用Lambda表達(dá)式

重新考慮printPersonsWithPredicate方法以查看可以使用lambda表達(dá)式的其他位置:

public static void printPersonsWithPredicate(
    List roster, Predicate tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

此方法檢查List參數(shù)roster中包含的每個(gè)Person實(shí)例是否滿足Predicate參數(shù)tester中指定的條件,如果Person實(shí)例滿足tester指定的條件,則在Person實(shí)例上調(diào)用printPersron方法。

你可以指定一個(gè)不同的操作來(lái)執(zhí)行那些滿足tester指定的條件的Person實(shí)例,而不是調(diào)用printPerson方法,你可以使用lambda表達(dá)式指定此操作。假設(shè)你想要一個(gè)類(lèi)似于printPerson的lambda表達(dá)式,它接受一個(gè)參數(shù)(Person類(lèi)型的對(duì)象)并返回void,請(qǐng)記住,要使用lambda表達(dá)式,你需要實(shí)現(xiàn)一個(gè)功能性接口。在這種情況下,你需要一個(gè)包含抽象方法的功能性接口,該方法可以接受一個(gè)Person類(lèi)型的參數(shù)并返回void。Consumer接口包含void accept(T t)方法,它具有這些特性,以下方法將調(diào)用p.printPerson()替換為調(diào)用方法acceptConsumer實(shí)例:

public static void processPersons(
    List roster,
    Predicate tester,
    Consumer block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                block.accept(p);
            }
        }
}

因此,以下方法調(diào)用與在方法3:在局部類(lèi)中指定搜索條件代碼以獲取有資格獲得選擇性服務(wù)的成員中調(diào)用printPersons時(shí)相同,用于打印成員的lambda表達(dá)式如下:

processPersons(
     roster,
     p -> p.getGender() == Person.Sex.MALE
         && p.getAge() >= 18
         && p.getAge() <= 25,
     p -> p.printPerson()
);

如果你想對(duì)成員的個(gè)人資料進(jìn)行更多操作而不是打印出來(lái),該怎么辦?假設(shè)你要驗(yàn)證成員的個(gè)人資料或檢索他們的聯(lián)系信息?在這種情況下,你需要一個(gè)包含返回值的抽象方法的功能性接口,Function接口包含方法R apply(T t),以下方法檢索參數(shù)mapper指定的數(shù)據(jù),然后對(duì)參數(shù)block指定的操作執(zhí)行操作:

public static void processPersonsWithFunction(
    List roster,
    Predicate tester,
    Function mapper,
    Consumer block) {
    for (Person p : roster) {
        if (tester.test(p)) {
            String data = mapper.apply(p);
            block.accept(data);
        }
    }
}

以下方法從有資格獲得選擇性服務(wù)的roster中包含的每個(gè)成員檢索電子郵件地址,然后將其打印出來(lái):

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);
方法8:更廣泛地使用泛型

重新考慮方法processPersonsWithFunction,以下是它的泛型版本,它接受包含任何數(shù)據(jù)類(lèi)型元素的集合作為參數(shù):

public static  void processElements(
    Iterable source,
    Predicate tester,
    Function  mapper,
    Consumer block) {
    for (X p : source) {
        if (tester.test(p)) {
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

要打印有資格獲得選擇性服務(wù)的成員的電子郵件地址,請(qǐng)按如下方式調(diào)用processElements方法:

processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

此方法調(diào)用執(zhí)行以下操作:

從集合source獲取對(duì)象源,在此示例中,它從集合roster中獲取Person對(duì)象的源,請(qǐng)注意,集合rosterList類(lèi)型的集合,也是Iterable類(lèi)型的對(duì)象。

過(guò)濾與Predicate對(duì)象tester匹配的對(duì)象,在此示例中,Predicate對(duì)象是一個(gè)lambda表達(dá)式,指定哪些成員有資格獲得選擇性服務(wù)。

將每個(gè)篩選對(duì)象映射到Function對(duì)象mapper指定的值,在此示例中,Function對(duì)象是一個(gè)lambda表達(dá)式,它返回成員的電子郵件地址。

對(duì)Consumer對(duì)象block指定的每個(gè)映射對(duì)象執(zhí)行操作,在此示例中,Consumer對(duì)象是一個(gè)lambda表達(dá)式,用于打印字符串,該字符串是Function對(duì)象返回的電子郵件地址。

你可以使用聚合操作替換每個(gè)操作。

方法9:使用接受Lambda表達(dá)式作為參數(shù)的聚合操作

以下示例使用聚合操作來(lái)打印有資格獲得選擇性服務(wù)的集合roster中包含的成員的電子郵件地址:

roster
    .stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

下表將方法processElements執(zhí)行的每個(gè)操作映射到相應(yīng)的聚合操作:

processElements 行動(dòng) 聚合操作
獲取對(duì)象的源 Stream stream()
過(guò)濾與Predicate對(duì)象匹配的對(duì)象 Stream filter(Predicate predicate)
將對(duì)象映射到Function對(duì)象指定的另一個(gè)值 Stream map(Function mapper)
執(zhí)行Consumer對(duì)象指定的操作 void forEach(Consumer action)

操作filtermapforEach是聚合操作,聚合操作處理流中的元素,而不是直接來(lái)自集合(這是本例中調(diào)用的第一個(gè)方法是stream的原因)。流是一系列元素,與集合不同,它不是存儲(chǔ)元素的數(shù)據(jù)結(jié)構(gòu),相反,流通過(guò)管道攜帶來(lái)自源(例如集合)的值,管道是一系列流操作,在此示例中為filter-map-forEach,此外,聚合操作通常接受lambda表達(dá)式作為參數(shù),使你可以自定義它們的行為方式。

有關(guān)聚合操作的更全面討論,請(qǐng)參閱聚合操作課程。

GUI應(yīng)用程序中的Lambda表達(dá)式

要處理圖形用戶界面(GUI)應(yīng)用程序中的事件,例如鍵盤(pán)操作、鼠標(biāo)操作和滾動(dòng)操作,通常會(huì)創(chuàng)建事件處理程序,這通常涉及實(shí)現(xiàn)特定的接口,通常,事件處理程序接口是功能性接口;他們往往只有一種方法。

在JavaFX示例HelloWorld.java中(在上一節(jié)匿名類(lèi)中討論過(guò)),你可以在此語(yǔ)句中用lambda表達(dá)式替換匿名類(lèi):

btn.setOnAction(new EventHandler() {

    @Override
    public void handle(ActionEvent event) {
        System.out.println("Hello World!");
    }
});

方法調(diào)用btn.setOnAction指定在選擇由btn對(duì)象表示的按鈕時(shí)會(huì)發(fā)生什么,此方法需要EventHandler類(lèi)型的對(duì)象,EventHandler接口只包含一個(gè)方法,void handle(T event),此接口是一個(gè)功能性接口,因此你可以使用以下顯示的lambda表達(dá)式來(lái)替換它:

btn.setOnAction(
   event -> System.out.println("Hello World!")
);
Lambda表達(dá)式的語(yǔ)法

lambda表達(dá)式包含以下內(nèi)容:

括號(hào)中用逗號(hào)分隔的形式參數(shù)列表,CheckPerson.test方法包含一個(gè)參數(shù)p,它表示Person類(lèi)的實(shí)例。
注意:你可以省略lambda表達(dá)式中參數(shù)的數(shù)據(jù)類(lèi)型,此外,如果只有一個(gè)參數(shù),則可以省略括號(hào),例如,以下lambda表達(dá)式也是有效的:

p -> p.getGender() == Person.Sex.MALE 
   && p.getAge() >= 18
   && p.getAge() <= 25

箭頭標(biāo)記,->

代碼體,由單個(gè)表達(dá)式或語(yǔ)句塊組成,此示例使用以下表達(dá)式:

p.getGender() == Person.Sex.MALE 
   && p.getAge() >= 18
   && p.getAge() <= 25

如果指定單個(gè)表達(dá)式,則Java運(yùn)行時(shí)將計(jì)算表達(dá)式,然后返回其值,或者,你可以使用return語(yǔ)句:

p -> {
    return p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25;
}

return語(yǔ)句不是表達(dá)式,在lambda表達(dá)式中,你必須將語(yǔ)句括在大括號(hào)({})中,但是,你不必在大括號(hào)中包含void方法調(diào)用,例如,以下是有效的lambda表達(dá)式:

email -> System.out.println(email)

請(qǐng)注意,lambda表達(dá)式看起來(lái)很像方法聲明,你可以將lambda表達(dá)式視為匿名方法 — 沒(méi)有名稱(chēng)的方法。

以下示例Calculator是lambda表達(dá)式的示例,它采用多個(gè)形式參數(shù):

public class Calculator {
  
    interface IntegerMath {
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
    
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}

方法operateBinary對(duì)兩個(gè)整數(shù)操作數(shù)執(zhí)行數(shù)學(xué)運(yùn)算,操作本身由IntegerMath實(shí)例指定,該示例使用lambda表達(dá)式,additionsubtraction定義了兩個(gè)操作,該示例打印以下內(nèi)容:

40 + 2 = 42
20 - 10 = 10
訪問(wèn)封閉范圍的局部變量

像局部和匿名類(lèi)一樣,lambda表達(dá)式可以捕獲變量,它們對(duì)封閉范圍的局部變量具有相同的訪問(wèn)權(quán)限,但是,與局部和匿名類(lèi)不同,lambda表達(dá)式?jīng)]有任何遮蔽問(wèn)題(有關(guān)更多信息,請(qǐng)參閱遮蔽),Lambda表達(dá)式具有詞法作用域。這意味著它們不會(huì)從超類(lèi)型繼承任何名稱(chēng)或引入新級(jí)別的范圍,lambda表達(dá)式中的聲明與封閉環(huán)境中的聲明一樣被解釋?zhuān)韵率纠齃ambdaScopeTest演示了這一點(diǎn):

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            
            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99;
            
            Consumer myConsumer = (y) -> 
            {
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };

            myConsumer.accept(x);

        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

此示例生成以下輸出:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

如果在lambda表達(dá)式myConsumer的聲明中用參數(shù)x代替y,則編譯器會(huì)生成錯(cuò)誤:

Consumer myConsumer = (x) -> {
    // ...
}

編譯器生成錯(cuò)誤“variable x is already defined in method methodInFirstLevel(int)”,因?yàn)閘ambda表達(dá)式不會(huì)引入新的作用域級(jí)別,因此,你可以直接訪問(wèn)封閉范圍的字段、方法和局部變量。例如,lambda表達(dá)式直接訪問(wèn)methodInFirstLevel方法的參數(shù)x,要訪問(wèn)封閉類(lèi)中的變量,請(qǐng)使用關(guān)鍵字this,在此示例中,this.x引用成員變量FirstLevel.x。

但是,與局部和匿名類(lèi)一樣,lambda表達(dá)式只能訪問(wèn)final或有效final的封閉塊的局部變量和參數(shù),例如,假設(shè)你在methodInFirstLevel定義語(yǔ)句之后立即添加以下賦值語(yǔ)句:

void methodInFirstLevel(int x) {
    x = 99;
    // ...
}

由于這個(gè)賦值語(yǔ)句,變量FirstLevel.x不再是final,因此,Java編譯器生成類(lèi)似于“l(fā)ocal variables referenced from a lambda expression must be final or effectively final”的錯(cuò)誤消息,其中l(wèi)ambda表達(dá)式myConsumer嘗試訪問(wèn)FirstLevel.x變量:

System.out.println("x = " + x);
目標(biāo)類(lèi)型

你如何確定lambda表達(dá)式的類(lèi)型?回想一下lambda表達(dá)式,它選擇了男性和年齡在18到25歲之間的成員:

p -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25

這個(gè)lambda表達(dá)式用于以下兩種方法:

方法3:在局部類(lèi)中指定搜索條件代碼中的public static void printPersons(List roster, CheckPerson tester)

方法6:將標(biāo)準(zhǔn)功能性接口與Lambda表達(dá)式一起使用中的public void printPersonsWithPredicate(List roster, Predicate tester)

當(dāng)Java運(yùn)行時(shí)調(diào)用方法printPersons時(shí),它期望CheckPerson的數(shù)據(jù)類(lèi)型,因此lambda表達(dá)式屬于這種類(lèi)型,但是,當(dāng)Java運(yùn)行時(shí)調(diào)用方法printPersonsWithPredicate時(shí),它期望數(shù)據(jù)類(lèi)型為Predicate,因此lambda表達(dá)式屬于此類(lèi)型。這些方法所期望的數(shù)據(jù)類(lèi)型稱(chēng)為目標(biāo)類(lèi)型,要確定lambda表達(dá)式的類(lèi)型,Java編譯器使用發(fā)現(xiàn)lambda表達(dá)式的上下文或情境的目標(biāo)類(lèi)型,因此,你只能在Java編譯器可以確定目標(biāo)類(lèi)型的情況下使用lambda表達(dá)式:

變量聲明

賦值

Return語(yǔ)句

數(shù)組初始化

方法或構(gòu)造函數(shù)參數(shù)

Lambda表達(dá)體

條件表達(dá)式,?:

轉(zhuǎn)換表達(dá)式

目標(biāo)類(lèi)型和方法參數(shù)

對(duì)于方法參數(shù),Java編譯器使用另外兩種語(yǔ)言功能確定目標(biāo)類(lèi)型:重載決策和類(lèi)型參數(shù)推斷。

考慮以下兩個(gè)功能性接口(java.lang.Runnable和java.util.concurrent.Callable):

public interface Runnable {
    void run();
}

public interface Callable {
    V call();
}

方法Runnable.run不返回值,而Callable.call則返回值。

假設(shè)你已按如下方式重載方法invoke(有關(guān)重載方法的詳細(xì)信息,請(qǐng)參閱定義方法):

void invoke(Runnable r) {
    r.run();
}

 T invoke(Callable c) {
    return c.call();
}

將在以下語(yǔ)句中調(diào)用哪個(gè)方法?

String s = invoke(() -> "done");

將調(diào)用方法invoke(Callable),因?yàn)樵摲椒ǚ祷匾粋€(gè)值,方法invoke(Runnable)沒(méi)有,在這種情況下,lambda表達(dá)式() -> "done"的類(lèi)型是Callable。

序列化

如果lambda表達(dá)式的目標(biāo)類(lèi)型及其捕獲的參數(shù)是可序列化的,則可以序列化它,但是,與內(nèi)部類(lèi)一樣,強(qiáng)烈建議不要對(duì)lambda表達(dá)式進(jìn)行序列化。

方法引用

你使用lambda表達(dá)式來(lái)創(chuàng)建匿名方法,但是,有時(shí),lambda表達(dá)式只會(huì)調(diào)用現(xiàn)有方法,在這些情況下,通過(guò)名稱(chēng)引用現(xiàn)有方法通常更清楚,方法引用使你可以這樣做;對(duì)于已經(jīng)有名稱(chēng)的方法,它們是緊湊的,易于閱讀的lambda表達(dá)式。

再次考慮Person類(lèi):

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar getBirthday() {
        return birthday;
    }    

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }}

假設(shè)你的社交網(wǎng)絡(luò)應(yīng)用程序的成員包含在一個(gè)數(shù)組中,并且你希望按年齡對(duì)數(shù)組進(jìn)行排序,你可以使用以下代碼(在示例MethodReferencesTest中查找本節(jié)中描述的代碼摘錄):

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

此調(diào)用排序的方法簽名如下:

static  void sort(T[] a, Comparator c)

請(qǐng)注意,Comparator接口是一個(gè)功能性接口,因此,你可以使用lambda表達(dá)式而不是定義然后創(chuàng)建實(shí)現(xiàn)Comparator的類(lèi)的新實(shí)例:

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

但是,這種比較兩個(gè)Person實(shí)例的出生日期的方法已經(jīng)存在為Person.compareByAge,你可以在lambda表達(dá)式的主體中調(diào)用此方法:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

因?yàn)榇薼ambda表達(dá)式調(diào)用現(xiàn)有方法,所以可以使用方法引用而不是lambda表達(dá)式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用Person::compareByAge在語(yǔ)義上與lambda表達(dá)式(a, b) -> Person.compareByAge(a, b)相同,每個(gè)都有以下特點(diǎn):

它的形式參數(shù)列表是從Comparator.compare復(fù)制的,它是(Person, Person)。

它的主體調(diào)用方法Person.compareByAge。

各種方法引用

有四種方法引用:

種類(lèi) 示例
引用靜態(tài)方法 ContainingClass::staticMethodName
引用特定對(duì)象的實(shí)例方法 containingObject::instanceMethodName
引用特定類(lèi)型的任意對(duì)象的實(shí)例方法 ContainingType::methodName
引用構(gòu)造函數(shù) ClassName::new
引用靜態(tài)方法

方法引用Person::compareByAge是對(duì)靜態(tài)方法的引用。

引用特定對(duì)象的實(shí)例方法

以下是對(duì)特定對(duì)象的實(shí)例方法的引用示例:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用myComparisonProvider::compareByName調(diào)用方法compareByName,它是對(duì)象myComparisonProvider的一部分,JRE推斷出方法類(lèi)型參數(shù),在本例中是 (Person, Person)。

對(duì)特定類(lèi)型的任意對(duì)象的實(shí)例方法的引用

以下是對(duì)特定類(lèi)型的任意對(duì)象的實(shí)例方法的引用示例:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用String::compareToIgnoreCase的等效lambda表達(dá)式將具有形式參數(shù)列表(String a, String b),其中ab是用于更好地描述此示例的任意名稱(chēng),方法引用將調(diào)用方法a.compareToIgnoreCase(b)。

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

你可以使用名稱(chēng)new以與靜態(tài)方法相同的方式引用構(gòu)造函數(shù),以下方法將元素從一個(gè)集合復(fù)制到另一個(gè)集合:

public static , DEST extends Collection>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

功能性接口Supplier包含一個(gè)不帶參數(shù)且返回對(duì)象的方法get,因此,你可以使用lambda表達(dá)式調(diào)用方法transferElements,如下所示:

Set rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

你可以使用構(gòu)造函數(shù)引用代替lambda表達(dá)式,如下所示:

Set rosterSet = transferElements(roster, HashSet::new);

Java編譯器推斷你要?jiǎng)?chuàng)建包含Person類(lèi)型元素的HashSet集合,或者,你可以按如下方式指定:

Set rosterSet = transferElements(roster, HashSet::new);
何時(shí)使用嵌套類(lèi)、局部類(lèi)、匿名類(lèi)和Lambda表達(dá)式

如嵌套類(lèi)一節(jié)所述,嵌套類(lèi)使你能夠?qū)H在一個(gè)地方使用的類(lèi)進(jìn)行邏輯分組,增加封裝的使用,并創(chuàng)建更易讀和可維護(hù)的代碼。局部類(lèi)、匿名類(lèi)和lambda表達(dá)式也賦予這些優(yōu)點(diǎn),但是,它們旨在用于更具體的情況:

局部類(lèi):如果你需要?jiǎng)?chuàng)建一個(gè)類(lèi)的多個(gè)實(shí)例,訪問(wèn)其構(gòu)造函數(shù)或引入新的命名類(lèi)型(例如,你需要稍后調(diào)用其他方法),請(qǐng)使用它。

匿名類(lèi):如果需要聲明字段或其他方法,請(qǐng)使用它。

lambda表達(dá)式:

如果要封裝希望傳遞給其他代碼的單個(gè)行為單元,請(qǐng)使用它,例如,如果要在集合的每個(gè)元素上執(zhí)行某個(gè)操作,或者在流程完成時(shí),或者在流程遇到錯(cuò)誤時(shí),你將使用lambda表達(dá)式。

如果你需要功能性接口的簡(jiǎn)單實(shí)例并且不應(yīng)用前述條件(例如,你不需要構(gòu)造函數(shù)、命名類(lèi)型、字段或其他方法),請(qǐng)使用它。

嵌套類(lèi):如果你的要求與局部類(lèi)的要求類(lèi)似,則需要使用它,你希望更廣泛地使用該類(lèi)型,并且不需要訪問(wèn)局部變量或方法參數(shù)。

如果需要訪問(wèn)封閉實(shí)例的非公共字段和方法,請(qǐng)使用非靜態(tài)嵌套類(lèi)(或內(nèi)部類(lèi)),如果不需要此訪問(wèn)權(quán)限,請(qǐng)使用靜態(tài)嵌套類(lèi)。

上一篇:匿名類(lèi) 下一篇:枚舉類(lèi)型

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72438.html

相關(guān)文章

  • Java 8 并發(fā)教程:原子變量和 ConcurrentMa

    摘要:并發(fā)教程原子變量和原文譯者飛龍協(xié)議歡迎閱讀我的多線程編程系列教程的第三部分。如果你能夠在多線程中同時(shí)且安全地執(zhí)行某個(gè)操作,而不需要關(guān)鍵字或上一章中的鎖,那么這個(gè)操作就是原子的。當(dāng)多線程的更新比讀取更頻繁時(shí),這個(gè)類(lèi)通常比原子數(shù)值類(lèi)性能更好。 Java 8 并發(fā)教程:原子變量和 ConcurrentMap 原文:Java 8 Concurrency Tutorial: Synchroni...

    bitkylin 評(píng)論0 收藏0
  • Java 8 簡(jiǎn)明教程

    摘要:簡(jiǎn)明教程原文譯者黃小非來(lái)源簡(jiǎn)明教程并沒(méi)有沒(méi)落,人們很快就會(huì)發(fā)現(xiàn)這一點(diǎn)歡迎閱讀我編寫(xiě)的介紹。編譯器會(huì)自動(dòng)地選擇合適的構(gòu)造函數(shù)來(lái)匹配函數(shù)的簽名,并選擇正確的構(gòu)造函數(shù)形式。 Java 8 簡(jiǎn)明教程 原文:Java 8 Tutorial 譯者:ImportNew.com - 黃小非 來(lái)源:Java 8簡(jiǎn)明教程 ? Java并沒(méi)有沒(méi)落,人們很快就會(huì)發(fā)現(xiàn)這一點(diǎn) 歡迎閱讀我編寫(xiě)的Java ...

    testHs 評(píng)論0 收藏0
  • Java? 教程(聚合操作)

    聚合操作 你使用集合做什么?你不可能簡(jiǎn)單地將對(duì)象存儲(chǔ)在集合中并將它們留在那里,在大多數(shù)情況下,使用集合檢索存儲(chǔ)在其中的項(xiàng)。 再次考慮Lambda表達(dá)式小節(jié)中描述的場(chǎng)景,假設(shè)你正在創(chuàng)建一個(gè)社交網(wǎng)絡(luò)應(yīng)用程序,你希望創(chuàng)建一個(gè)功能,使管理員能夠?qū)M足某些條件的社交網(wǎng)絡(luò)應(yīng)用程序的成員執(zhí)行任何類(lèi)型的操作,例如發(fā)送消息。 如前所述,假設(shè)這個(gè)社交網(wǎng)絡(luò)應(yīng)用程序的成員由以下Person類(lèi)表示: public clas...

    DDreach 評(píng)論0 收藏0
  • 樂(lè)字節(jié)-Java8核心特性實(shí)戰(zhàn)之Lambda達(dá)式

    摘要:使用表達(dá)式,使得應(yīng)用變得簡(jiǎn)潔而緊湊。很多語(yǔ)言等從設(shè)計(jì)之初就支持表達(dá)式。表達(dá)式的參數(shù)與函數(shù)式接口內(nèi)方法的參數(shù),返回值類(lèi)型相互對(duì)應(yīng)。更多教程和資料請(qǐng)上騰訊課堂樂(lè)字節(jié) showImg(https://segmentfault.com/img/bVbtotg?w=935&h=345); Java8 引入Lambda表達(dá)式,允許開(kāi)發(fā)者將函數(shù)當(dāng)成參數(shù)傳遞給某個(gè)方法,或者把代碼本身當(dāng)作數(shù)據(jù)進(jìn)行處理。...

    Karuru 評(píng)論0 收藏0
  • Java 8 并發(fā)教程:線程和執(zhí)行器

    摘要:在這個(gè)示例中我們使用了一個(gè)單線程線程池的。在延遲消逝后,任務(wù)將會(huì)并發(fā)執(zhí)行。這是并發(fā)系列教程的第一部分。第一部分線程和執(zhí)行器第二部分同步和鎖第三部分原子操作和 Java 8 并發(fā)教程:線程和執(zhí)行器 原文:Java 8 Concurrency Tutorial: Threads and Executors 譯者:BlankKelly 來(lái)源:Java8并發(fā)教程:Threads和Execut...

    jsdt 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<