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
本節(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(Listroster, 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( Listroster, 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( Listroster, 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
interface Predicate{ boolean test(T t); }
接口Predicate
interface Predicate{ boolean test(Person t); }
此參數(shù)化類(lèi)型包含一個(gè)方法,該方法具有與CheckPerson.boolean test(Person p)相同的返回類(lèi)型和參數(shù),因此,你可以使用Predicate
public static void printPersonsWithPredicate( Listroster, 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( Listroster, 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
public static void processPersons( Listroster, 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
public static void processPersonsWithFunction( Listroster, 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 staticvoid 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)注意,集合roster是List類(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 |
過(guò)濾與Predicate對(duì)象匹配的對(duì)象 | Stream |
將對(duì)象映射到Function對(duì)象指定的另一個(gè)值 | |
執(zhí)行Consumer對(duì)象指定的操作 | void forEach(Consumer super T> action) |
操作filter、map和forEach是聚合操作,聚合操作處理流中的元素,而不是直接來(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
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á)式,addition和subtraction定義了兩個(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; ConsumermyConsumer = (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ò)誤:
ConsumermyConsumer = (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
方法6:將標(biāo)準(zhǔn)功能性接口與Lambda表達(dá)式一起使用中的public void printPersonsWithPredicate(List
當(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
變量聲明
賦值
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
假設(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
如果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)用排序的方法簽名如下:
staticvoid sort(T[] a, Comparator super T> 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
它的主體調(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 |
方法引用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),其中a和b是用于更好地描述此示例的任意名稱(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,如下所示:
SetrosterSetLambda = transferElements(roster, () -> { return new HashSet<>(); });
你可以使用構(gòu)造函數(shù)引用代替lambda表達(dá)式,如下所示:
SetrosterSet = transferElements(roster, HashSet::new);
Java編譯器推斷你要?jiǎng)?chuàng)建包含Person類(lèi)型元素的HashSet集合,或者,你可以按如下方式指定:
Set何時(shí)使用嵌套類(lèi)、局部類(lèi)、匿名類(lèi)和Lambda表達(dá)式rosterSet = transferElements(roster, HashSet ::new);
如嵌套類(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
摘要:并發(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...
摘要:簡(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 ...
聚合操作 你使用集合做什么?你不可能簡(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...
摘要:使用表達(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)行處理。...
摘要:在這個(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...
閱讀 1796·2021-09-26 09:46
閱讀 3056·2021-09-22 15:55
閱讀 2640·2019-08-30 14:17
閱讀 3060·2019-08-26 11:59
閱讀 1843·2019-08-26 11:35
閱讀 3181·2019-08-26 10:45
閱讀 3180·2019-08-23 18:28
閱讀 1199·2019-08-23 18:21