Java lambda expression

Lambda表達式

匿名類的一個問題是,若是匿名類的實現很是簡單,例如只包含一個方法的接口,那麼匿名類的語法可能看起來不實用且不清楚。在這些狀況下,您一般會嘗試將功能做爲參數傳遞給另外一個方法,例如當有人單擊按鈕時應採起的操做。Lambda表達式使您能夠執行此操做,將功能視爲方法參數,或將代碼視爲數據。html

Lambda表達式

匿名類的一個問題是,若是匿名類的實現很是簡單,例如只包含一個方法的接口,那麼匿名類的語法可能看起來不實用且不清楚。在這些狀況下,您一般會嘗試將功能做爲參數傳遞給另外一個方法,例如當有人單擊按鈕時應採起的操做。Lambda表達式使您能夠執行此操做,將功能視爲方法參數,或將代碼視爲數據。算法

上一節「 匿名類 」向您展現瞭如何在不給它命名的狀況下實現基類。雖然這一般比命名類更簡潔,但對於只有一個方法的類,即便是匿名類也彷佛有點過度和繁瑣。Lambda表達式容許您更緊湊地表達單方法類的實例。express

Lambda表達式的理想用例

假設您正在建立社交網絡應用程序。您但願建立一項功能,使管理員可以對知足特定條件的社交網絡應用程序成員執行任何類型的操做,例如發送消息。下表詳細描述了此用例:api

領域 描述
名稱 對選定的成員執行操做
主要演員 管理員
前提條件 管理員已登陸系統。
後置條件 僅對符合指定條件的成員執行操做。
主要成功案例
  1. 管理員指定要執行特定操做的成員的條件。
  2. 管理員指定要對這些選定成員執行的操做。
  3. 管理員選擇「 提交」按鈕。
  4. 系統將查找與指定條件匹配的全部成員。
  5. 系統對全部匹配成員執行指定的操做。
擴展

1A。管理員能夠選擇在指定要執行的操做以前或選擇「 提交」按鈕以前預覽符合指定條件的成員。數組

發生頻率 白天不少次。

假設此社交網絡應用程序的成員由如下Person類表示 :網絡

公共類人{

    public enum Sex {
        男,女
    }

    字符串名稱;
    LocalDate生日;
    性別;
    字符串emailAddress;

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

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

假設您的社交網絡應用程序的成員存儲在一個List<Person>實例中。方法我介紹一種,其餘的能夠查看其官網。oracle

建立搜索匹配一個特徵的成員的方法

一種簡單的方法是建立幾種方法; 每種方法都會搜索與一個特徵匹配的成員,例如性別或年齡。如下方法打印超過指定年齡的成員:app

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

注意:A List是有序的 Collection。甲集合是一個對象,該組中的多個元素到單個單元中。集合用於存儲,檢索,操做和傳遞聚合數據。有關集合的更多信息,請參閱 集合跟蹤。ide

這種方法可能會使您的應用程序變得脆弱,這是因爲引入了更新(例如更新的數據類型)致使應用程序沒法工做的可能性。假設您升級應用程序並更改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表達式兩個操做,additionsubtraction。該示例打印如下內容:

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表達式用於如下兩種方法:

當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),其中ab是用於更好地描述此示例的任意名稱。方法引用將調用該方法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);
相關文章
相關標籤/搜索