詳解Lambda表達式

引言:
函數式編程思想概述:在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是「拿什麼東西作什麼事情」。相對而言,面向對象過度強調「必須經過對象的形式來作事情」,而函數式思想則儘可能忽略面向對象的複雜語法——強調作什麼,而不是以什麼形式作。作什麼,而不是怎麼作,咱們真的但願建立一個匿名內部類對象嗎?不。咱們只是爲了作這件事情而不得不建立一個對象。java

咱們真正但願作的事情是:將 run 方法體內的代碼傳遞給 Thread 類知曉。
傳遞一段代碼——這纔是咱們真正的目的。而建立對象只是受限於面向對象語法而不得不採起的一種手段方式。那,有沒有更加簡單的辦法?若是咱們將關注點從「怎麼作」迴歸到「作什麼」的本質上,就會發現只要可以更好地達到目的,過程與形式其實並不重要。編程

瞭解Lambda的優化
當須要啓動一個線程去完成任務時,一般會經過 java.lang.Runnable 接口來定義任務內容,並使用 java.lang.Thread 類來啓動該線程。
傳統寫法,代碼以下:數組

/**
 * @Author:Auser·傑
 * @DATE:2019/11/4 21:50
 */
public class Demo07ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {new Person("墨白", 19),
                              new Person("小檸檬不酸", 18),
                              new Person("大白", 20) };
        Arrays.sort(array, (Person a, Person b) -> {
         return a.getAge() - b.getAge();
       });
        for (Person person : array) {
            System.out.println(person);
       }
   }
}

本着「一切皆對象」的思想,這種作法是無可厚非的:首先建立一個 Runnable 接口的匿名內部類對象來指定任務內容,再將其交給一個線程來啓動。多線程

代碼分析:
對於 Runnable 的匿名內部類用法,能夠分析出幾點內容:
Thread 類須要 Runnable 接口做爲參數,其中的抽象 run 方法是用來指定線程任務內容的核心;
爲了指定 run 的方法體,不得不須要 Runnable 接口的實現類;
爲了省去定義一個 RunnableImpl 實現類的麻煩,不得不使用匿名內部類;
必須覆蓋重寫抽象 run 方法,因此方法名稱、方法參數、方法返回值不得再也不寫一遍,且不能寫錯;
而實際上,彷佛只有方法體纔是關鍵所在。框架

Lambda表達式寫法,
代碼以下:
藉助Java 8的全新語法,上述 Runnable 接口的匿名內部類寫法能夠經過更簡單的Lambda表達式達到等效:ide

1/**
2 * @Author:Auser·傑
3 * @DATE:2019/11/4 21:50
4 */
5public class Demo02LambdaRunnable {
6  public static void main(String[] args) {         // 啓動線程
7    new Thread(() -> System.out.println("多線程任務執行!")).start(); 
8  }
9}

這段代碼和剛纔的執行效果是徹底同樣的,能夠在1.8或更高的編譯級別下經過。從代碼的語義中能夠看出:咱們啓動了一個線程,而線程任務的內容以一種更加簡潔的形式被指定。再也不有「不得不建立接口對象」的束縛,再也不有「抽象方法覆蓋重寫」的負擔,就是這麼簡單!函數式編程

Lambda的格式
標準格式:
Lambda省去面向對象的條條框框,格式由3個部分組成:
一些參數
一個箭頭
一段代碼
Lambda表達式的標準格式爲:
1(參數類型 參數名稱) -> { 代碼語句 }
格式說明:
小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。
-> 是新引入的語法格式,表明指向動做。
大括號內的語法與傳統方法體要求基本一致。
匿名內部類與lambda對比:函數

1/**
 2 * @Author:Auser·傑
 3 * @DATE:2019/11/4 21:58
 4 */
 5new Thread(new Runnable() {
 6@Override
 7public void run() {
 8    System.out.println("多線程任務執行!");
 9    }
10    }).start()

仔細分析該代碼中, Runnable 接口只有一個 run 方法的定義:
public abstract void run();即制定了一種作事情的方案(其實就是一個方法):
無參數:不須要任何條件便可執行該方案。
無返回值:該方案不產生任何結果。
代碼塊(方法體):該方案的具體執行步驟。
一樣的語義體如今 Lambda 語法中,要更加簡單:
1() -> System.out.println("多線程任務執行!")
前面的一對小括號即 run 方法的參數(無),表明不須要任何條件;
中間的一個箭頭表明將前面的參數傳遞給後面的代碼;
後面的輸出語句即業務邏輯代碼。
Lambda 參數和返回
下面舉例演示 java.util.Comparator接口的使用場景代碼,其中的抽象方法定義爲:
public abstract int compare(T o1, T o2);
當須要對一個對象數組進行排序時, Arrays.sort 方法須要一個 Comparator 接口實例來指定排序的規則。假設有一個 Person 類,含有 String name 和 int age 兩個成員變量:優化

1public class Person {
2    private String name;
3    private int age;
4
5    // 省略構造器、toString方法與Getter Setter
6}

傳統寫法若是使用傳統的代碼對 Person[] 數組進行排序,寫法以下:線程

1/**
 2 * @Author:Auser·傑
 3 * @DATE:2019/11/4 22:35
 4 */
 5public class TestComparator {
 6  public static void main(String[] args) {
 7     // 原本年齡亂序的對象數組
 8        Person[] array = {new Person("墨白", 19),
 9                          new Person("小檸檬不酸", 18),
10                          new Person("大白", 20) };
11     // 匿名內部類
12     Comparator<Person> comp = new Comparator<Person>() {
13            @Override
14      public int compare(Person o1, Person o2) {
15    return o1.getAge() - o2.getAge();
16      }
17    };
18        Arrays.sort(array, comp);
19       // 第二個參數爲排序規則,即Comparator接口實例
20        for (Person person : array) {
21            System.out.println(person);
22      }
23  }
24}

這種作法在面向對象的思想中,彷佛也是「理所固然」的。其中 Comparator 接口的實例(使用了匿名內部類)表明了「按照年齡從小到大」的排序規則。
代碼分析:
下面咱們來搞清楚上述代碼真正要作什麼事情。
爲了排序, Arrays.sort 方法須要排序規則,即 Comparator 接口的實例,抽象方法compare 是關鍵;
爲了指定 compare 的方法體,不得不須要 Comparator 接口的實現類;
爲了省去定義一個 ComparatorImpl 實現類的麻煩,不得不使用匿名內部類;
必須覆蓋重寫抽象 compare 方法,因此方法名稱、方法參數、方法返回值不得再也不寫一遍,且不能寫錯;
實際上,只有參數和方法體纔是關鍵。
Lambda寫法

1/**
 2 * @Author:Auser·傑
 3 * @DATE:2019/11/4 21:50
 4 */
 5public class TestComparatorLambda {
 6    public static void main(String[] args) {
 7        Person[] array = {new Person("墨白", 19),
 8                          new Person("小檸檬不酸", 18),
 9                          new Person("大白", 20) };
10        Arrays.sort(array, (Person a, Person b) -> {
11         return a.getAge() - b.getAge();
12       });
13        for (Person person : array) {
14            System.out.println(person);
15       }
16   }
17}

省略規則
在Lambda標準格式的基礎上,使用省略寫法的規則爲:

  1. 小括號內參數的類型能夠省略;
  2. 若是小括號內有且僅有一個參,則小括號能夠省略;
  3. 若是大括號內有且僅有一個語句,則不管是否有返回值,均可以省略大括號,return關鍵字及語句分號。

備註:掌握這些省略規則後,請對應地回顧本文開頭的多線程案例

可推導便可省略
Lambda強調的是「作什麼」而不是「怎麼作」,因此凡是能夠根據上下文推導得知的信息,均可以省略。例如上例還能夠使用Lambda的省略寫法:
Runnable接口簡化:

  1. () -> System.out.println("多線程任務執行!")

2.Comparator接口簡化:
3.. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge());
Lambda的前提
Lambda的語法很是簡潔,徹底沒有面向對象複雜的束縛。可是使用時有幾個問題須要特別注意:
使用Lambda必須具備接口,且要求接口中有且僅有一個抽象方法。 不管是JDK內置的Runnable 、 Comparator 接口仍是自定義的接口,只有當接口中的抽象方法存在且惟一時,才能夠使用Lambda。
使用Lambda必須具備上下文推斷。 也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda做爲該接口的實例。
備註:有且僅有一個抽象方法的接口,稱爲「函數式接口」。

今天的內容就到這裏了,下一次咱們來聊聊PageHelper,若是本文有啓發到你,請多多轉發點在看;

本文由公衆號【框架師 ,ID:mohu121】首發,轉載請註明出處
相關文章
相關標籤/搜索