Java lambda expression
Lambda表達式
匿名類的一個問題是,如果匿名類的實現非常簡單,例如只包含一個方法的接口,那么匿名類的語法可能看起來不實用且不清楚。在這些情況下,您通常會嘗試將功能作為參數傳遞給另一個方法,例如當有人單擊按鈕時應采取的操作。Lambda表達式使您可以執行此操作,將功能視為方法參數,或將代碼視為數據。
一頁???小道???下一頁?Java教程是為JDK 8編寫的。本頁描述的示例和實踐沒有利用后續版本中引入的改進。
Lambda表達式
匿名類的一個問題是,如果匿名類的實現非常簡單,例如只包含一個方法的接口,那么匿名類的語法可能看起來不實用且不清楚。在這些情況下,您通常會嘗試將功能作為參數傳遞給另一個方法,例如當有人單擊按鈕時應采取的操作。Lambda表達式使您可以執行此操作,將功能視為方法參數,或將代碼視為數據。
上一節“?匿名類?”向您展示了如何在不給它命名的情況下實現基類。雖然這通常比命名類更簡潔,但對于只有一個方法的類,即使是匿名類也似乎有點過分和繁瑣。Lambda表達式允許您更緊湊地表達單方法類的實例。
Lambda表達式的理想用例
假設您正在創建社交網絡應用程序。您希望創建一項功能,使管理員能夠對滿足特定條件的社交網絡應用程序成員執行任何類型的操作,例如發送消息。下表詳細描述了此用例:
| 名稱 | 對選定的成員執行操作 |
| 主要演員 | 管理員 |
| 前提條件 | 管理員已登錄系統。 |
| 后置條件 | 僅對符合指定條件的成員執行操作。 |
| 主要成功案例 | |
| 擴展 | 1A。管理員可以選擇在指定要執行的操作之前或選擇“?提交”按鈕之前預覽符合指定條件的成員。 |
| 發生頻率 | 白天很多次。 |
假設此社交網絡應用程序的成員由以下Person類表示?:
公共類人{public enum Sex {男,女}字符串名稱;LocalDate生日;性別;字符串emailAddress;public int getAge(){// ...}public void printPerson(){// ...} }假設您的社交網絡應用程序的成員存儲在一個List<Person>實例中。方法我介紹一種,其他的可以查看其官網。
創建搜索匹配一個特征的成員的方法
一種簡單的方法是創建幾種方法;?每種方法都會搜索與一個特征匹配的成員,例如性別或年齡。以下方法打印超過指定年齡的成員:
public static void printPersonsOlderThan(List <Person> roster,int age){for(Person p:roster){if(p.getAge()> = age){p.printPerson();}} }注意:A?List是有序的?Collection。甲集合是一個對象,該組中的多個元素到單個單元中。集合用于存儲,檢索,操作和傳遞聚合數據。有關集合的更多信息,請參閱?集合跟蹤。
這種方法可能會使您的應用程序變得脆弱,這是由于引入了更新(例如更新的數據類型)導致應用程序無法工作的可能性。假設您升級應用程序并更改Person類的結構,使其包含不同的成員變量;?也許該類記錄和測量年齡與不同的數據類型或算法。您必須重寫大量API以適應此更改。此外,這種方法是不必要的限制;?例如,如果您想要打印年齡小于某個年齡的成員,該怎么辦?
Lambda表達式的語法
lambda表達式包含以下內容:
-
括號中用逗號分隔的形式參數列表。該CheckPerson.test方法包含一個參數,?p表示Person該類的實例?。
注意:您可以省略lambda表達式中參數的數據類型。此外,如果只有一個參數,則可以省略括號。例如,以下lambda表達式也是有效的:
p - > p.getGender()== Person.Sex.MALE && p.getAge()> = 18&& p.getAge()<= 25 -
箭頭標記,?->
-
一個主體,由單個表達式或語句塊組成。此示例使用以下表達式:
p.getGender()== Person.Sex.MALE && p.getAge()> = 18&& p.getAge()<= 25如果指定單個表達式,則Java運行時將計算表達式,然后返回其值。或者,您可以使用return語句:
p - > {return p.getGender()== Person.Sex.MALE&& p.getAge()> = 18&& p.getAge()<= 25; }return語句不是表達式;?在lambda表達式中,必須用braces({})括起語句。但是,您不必在大括號中包含void方法調用。例如,以下是有效的lambda表達式:
電子郵件 - > System.out.println(電子郵件)
請注意,lambda表達式看起來很像方法聲明;?您可以將lambda表達式視為匿名方法 - 沒有名稱的方法。
以下示例?Calculator是一個lambda表達式的示例,它采用多個形式參數:
公共類計算器{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){計算器myApp = new Calculator();IntegerMath add =(a,b) - > a + b;IntegerMath減法=(a,b) - > a - b;System.out.println(“40 + 2 =”+myApp.operateBinary(40,2,另外));System.out.println(“20 - 10 =”+myApp.operateBinary(20,10,減法)); } }該方法operateBinary對兩個整數操作數執行數學運算。操作本身由實例指定IntegerMath。的例子中定義了lambda表達式兩個操作,addition和subtraction。該示例打印以下內容:
40 + 2 = 42 20 - 10 = 10訪問封閉范圍的局部變量
像本地和匿名類一樣,lambda表達式可以?捕獲變量?;?它們對封閉范圍的局部變量具有相同的訪問權限。但是,與本地和匿名類不同,lambda表達式沒有任何陰影問題(有關更多信息,請參閱?陰影)。Lambda表達式是詞法范圍的。這意味著它們不會從超類型繼承任何名稱或引入新級別的范圍。lambda表達式中的聲明與封閉環境中的聲明一樣被解釋。以下示例?LambdaScopeTest演示了這一點:
import java.util.function.Consumer;公共類LambdaScopeTest {public int x = 0;class FirstLevel {public int x = 1;void methodInFirstLevel(int x){//以下語句導致編譯器生成//錯誤“從lambda表達式引用的局部變量//必須是最終的或有效的最終“在聲明A中://// x = 99;消費者<整數> myConsumer =(y) - > {System.out.println(“x =”+ x); //聲明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表達式的聲明中替換參數x代替,則編譯器會生成錯誤:ymyConsumer
消費者<整數> myConsumer =(x) - > {// ... }編譯器生成錯誤“變量x已在方法methodInFirstLevel(int)中定義”,因為lambda表達式不會引入新的作用域級別。因此,您可以直接訪問封閉范圍的字段,方法和局部變量。例如,lambda表達式直接訪問x方法的參數methodInFirstLevel。要訪問封閉類中的變量,請使用關鍵字this。在此示例中,this.x引用成員變量FirstLevel.x。
但是,與本地和匿名類一樣,lambda表達式只能訪問最終或有效最終的封閉塊的局部變量和參數。例如,假設您在methodInFirstLevel定義語句后立即添加以下賦值語句:
void methodInFirstLevel(int x){x = 99;// ... }由于這個賦值語句,變量FirstLevel.x不再是有效的最終結果。因此,Java編譯器生成類似于“從lambda表達式引用的局部變量必須是final或者final final”的錯誤消息,其中lambda表達式myConsumer嘗試訪問FirstLevel.x變量:
System.out.println(“x =”+ x);目標打字
你如何確定lambda表達式的類型?回想一下lambda表達式,它選擇了男性和年齡在18到25歲之間的成員:
p - > p.getGender()== Person.Sex.MALE&& p.getAge()> = 18&& p.getAge()<= 25這個lambda表達式用于以下兩種方法:
-
public static void printPersons(List<Person> roster, CheckPerson tester)在方法3:在局部類指定搜索條件碼
-
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)在方法6:Lambda表達式使用標準的功能接口
當Java運行時調用該方法時printPersons,它期望數據類型為CheckPerson,因此lambda表達式屬于此類型。但是,當Java運行時調用該方法時printPersonsWithPredicate,它期望數據類型為Predicate<Person>,因此lambda表達式屬于此類型。這些方法所期望的數據類型稱為目標類型。要確定lambda表達式的類型,Java編譯器將使用發現lambda表達式的上下文或情境的目標類型。因此,您只能在Java編譯器可以確定目標類型的情況下使用lambda表達式:
-
變量聲明
-
分配
-
退貨聲明
-
數組初始化器
-
方法或構造函數參數
-
Lambda表達體
-
條件表達式,??:
-
轉換表達式
目標類型和方法參數
對于方法參數,Java編譯器使用另外兩種語言特性確定目標類型:重載解析和類型參數推斷。
考慮以下兩個功能接口(?java.lang.Runnable和?java.util.concurrent.Callable<V>):
public interface Runnable {void run(); }公共接口Callable <V> {V call(); }該方法Runnable.run不返回值,而是返回值Callable<V>.call。
假設您已invoke按如下方式重載方法(有關重載方法的詳細信息,請參閱?定義方法):
void invoke(Runnable r){r.run(); }<T> T invoke(Callable <T> c){return c.call(); }將在以下語句中調用哪個方法?
String s = invoke(() - >“done”);該方法invoke(Callable<T>)將被調用因為該方法返回的值;?方法?invoke(Runnable)沒有。在這種情況下,lambda表達式的類型() -> "done"是Callable<T>。
序列化
如果lambda表達式的目標類型及其捕獲的參數是可序列化的,則可以?序列化它。但是,與?內部類一樣,強烈建議不要對lambda表達式進行序列化。
各種方法參考
有四種方法參考:
| 參考靜態方法 | ContainingClass::staticMethodName |
| 引用特定對象的實例方法 | containingObject::instanceMethodName |
| 引用特定類型的任意對象的實例方法 | ContainingType::methodName |
| 引用構造函數 | ClassName::new |
參考靜態方法
方法引用Person::compareByAge是對靜態方法的引用。
引用特定對象的實例方法
以下是對特定對象的實例方法的引用示例:
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調用compareByName作為對象一部分的方法myComparisonProvider。JRE推斷出方法類型參數,在本例中是(Person, Person)。
對特定類型的任意對象的實例方法的引用
以下是對特定類型的任意對象的實例方法的引用示例:
String [] stringArray = {“芭芭拉”,“詹姆斯”,“瑪麗”,“約翰”,“Patricia”,“Robert”,“Michael”,“Linda”}; Arrays.sort(stringArray,String :: compareToIgnoreCase);方法引用的等效lambda表達式String::compareToIgnoreCase將具有形式參數列表(String a, String b),其中a和b是用于更好地描述此示例的任意名稱。方法引用將調用該方法a.compareToIgnoreCase(b)。
參考構造函數
您可以使用名稱以與靜態方法相同的方式引用構造函數new。以下方法將元素從一個集合復制到另一個集合:
public static <T,SOURCE擴展Collection <T>,DEST擴展Collection <T >>DEST transferElements(SOURCE sourceCollection,供應商<DEST> collectionFactory){DEST result = collectionFactory.get();for(T t:sourceCollection){result.add(T);}返回結果; }功能接口Supplier包含一個get不帶參數并返回對象的方法。因此,您可以transferElements使用lambda表達式調用該方法,如下所示:
設置<Person> rosterSetLambda =transferElements(roster,() - > {return new HashSet <>();});您可以使用構造函數引用代替lambda表達式,如下所示:
設置<Person> rosterSet = transferElements(roster,HashSet :: new);Java編譯器推斷您要創建HashSet包含類型元素的集合Person。或者,您可以按如下方式指定:
設置<Person> rosterSet = transferElements(名冊,HashSet <Person> :: new);?
轉載于:https://www.cnblogs.com/xasdh/p/10762717.html
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的Java lambda expression的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS3动画常用贝塞尔曲线-效果演示
- 下一篇: 人生实苦,可这就是人生