Java基礎-Lambda表達式篇

1. 函數式編程介紹

函數.png

在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是「拿什麼東西作什麼事情」。相對而言,面向對象過java

分強調「必須經過對象的形式來作事情」,而函數式思想則儘可能忽略面向對象的複雜語法——強調作什麼,而不是以編程

什麼形式作。數組

面向對象的思想: 作一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情。多線程

函數式編程思想: 只要能獲取到結果,誰去作的,怎麼作的都不重要,重視的是結果,不重視過程 。ide

2. 冗餘的Runnable代碼

  • 傳統寫法:函數式編程

    // 當須要啓動一個線程去完成任務時,一般會經過 java.lang.Runnable 接口來定義任務內容,並使用 java.lang.Thread 類來啓動該線程。代碼以下:
    public class Demo01Runnable { 
        public static void main(String[] args) { 
            // 匿名內部類 
            Runnable task = new Runnable() { 
                @Override 
                public void run() {
                  // 覆蓋重寫抽象方法 
                  System.out.println("多線程任務執行!");
                } 
            };
            new Thread(task).start(); // 啓動線程 
        }
    }
    // 本着「一切皆對象」的思想,這種作法是無可厚非的:首先建立一個 Runnable 接口的匿名內部類對象來指定任務內 容,再將其交給一個線程來啓動。
  • 代碼分析:函數

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

3. 編程思想轉換

  • 作什麼,而不是怎麼作線程

    ​ 咱們真的但願建立一個匿名內部類對象嗎?不。咱們只是爲了作這件事情而不得不建立一個對象。咱們真正但願作的事情是:將 run 方法體內的代碼傳遞給 Thread 類知曉。3d

    ​ 傳遞一段代碼——這纔是咱們真正的目的。而建立對象只是受限於面向對象語法而不得不採起的一種手段方式。code

    ​ 那有沒有更加簡單的辦法?若是咱們將關注點從「怎麼作」迴歸到「作什麼」的本質上,就會發現只要可以更好地達 到目的,過程與形式其實並不重要

  • 生活舉例

    交通.png

    ​ 當咱們須要從北京到上海時,能夠選擇高鐵、汽車、騎行或是徒步。咱們的真正目的是到達上海,而如何才能到達上海的形式並不重要,因此咱們一直在探索有沒有比高鐵更好的方式——搭乘飛機。

    交通

    ​ 而如今這種飛機(甚至是飛船)已經誕生:2014年3月Oracle所發佈的Java 8(JDK 1.8)中,加入了Lambda表達式的重量級新特性,爲咱們打開了新世界的大門。

4. 體驗Lambda的更優寫法

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

public class Test {
  public static void main(String[] args) {
    new Thread(()->System.out.println("多線程任務執行!")).start();
  }
}

​ 這段代碼和剛纔的執行效果是徹底同樣的,能夠在1.8或更高的編譯級別下經過。從代碼的語義中能夠看出:咱們啓動了一個線程,而線程任務的內容以一種更加簡潔的形式被指定。

​ 再也不有「不得不建立接口對象」的束縛,再也不有「抽象方法覆蓋重寫」的負擔,就是這麼簡單!

5. Lambda標準格式

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

6. 練習1:無參數無返回值的代碼

  • 題目

    • 給定一個廚子 Cook 接口,內含惟一的抽象方法 makeFood ,且無參數、無返回值。以下:

    • 代碼:

      public interface Cook { void makeFood(); }
      // 在下面的代碼中,請使用Lambda的標準格式調用 invokeCook 方法,打印輸出「吃飯啦!」字樣:
      public class Demo05InvokeCook { 
          public static void main(String[] args) { 
              // TODO 請在此使用Lambda【標準格式】調用invokeCook方法 
          }
          private static void invokeCook(Cook cook) { 
              cook.makeFood(); 
          } 
      }
  • 解答:

    invokeCook(() ‐> { System.out.println("吃飯啦!"); });

7. 練習2:有參數有返回值的代碼

  • 題目:使用數組存儲多個Person對象對數組中的Person對象使用Arrays的sort方法經過年齡進行升序排序

    下面舉例演示 java.util.Comparator<T>接口的使用場景代碼,其中的抽象方法定義爲:

    • public abstract int compare(T o1, T o2);

    當須要對一個對象數組進行排序時, Arrays.sort 方法須要一個 Comparator接口實例來指定排序的規則。假設有一個 Person 類,含有 String name 和 int age 兩個成員變量:

    public class Person { 
        private String name; 
        private int age; 
        // 省略構造器、toString方法與Getter Setter 
    }
  • 傳統方式:

    public class Test {
      public static void main(String[] args) {
        // 原本年齡亂序的對象數組
        Person[] array = {
                new Person("古力娜扎", 19),
                new Person("迪麗熱巴", 18),
                new Person("馬爾扎哈", 20)
        };
        // 排序
        Arrays.sort(array, new Comparator<Person>() {
          @Override
          public int compare(Person o1, Person o2) {
            return o1.getAge() - o2.getAge();
          }
        });
        // 打印
        for (int i = 0; i < array.length; i++) {
          System.out.println(array[i].getName() + "---" + array[i].getAge());
        }
      }
    }
    
    // 這種作法在面向對象的思想中,彷佛也是「理所固然」的。其中 Comparator 接口的實例(使用了匿名內部類)表明 了「按照年齡從小到大」的排序規則。
  • 代碼分析:下面咱們來搞清楚上述代碼真正要作什麼事情。

    1. 爲了排序, Arrays.sort 方法須要排序規則,即 Comparator 接口的實例,抽象方法 compare 是關鍵;
    2. 爲了指定 compare 的方法體,不得不須要 Comparator 接口的實現類;
    3. 爲了省去定義一個 ComparatorImpl 實現類的麻煩,不得不使用匿名內部類;
    4. 必須覆蓋重寫抽象 compare 方法,因此方法名稱、方法參數、方法返回值不得再也不寫一遍,且不能寫錯;
    5. 實際上,只有參數和方法體纔是關鍵。
  • Lambda方式:

    public class Test {
      public static void main(String[] args) {
        // 原本年齡亂序的對象數組
        Person[] array = {
                new Person("古力娜扎", 19),
                new Person("迪麗熱巴", 18),
                new Person("馬爾扎哈", 20)
        };
        // 排序
        Arrays.sort(array,(Person o1, Person o2) ->{
          return o1.getAge()-o2.getAge();
        });
        // 打印
        for (int i = 0; i < array.length; i++) {
          System.out.println(array[i].getName() + "---" + array[i].getAge());
        }
      }
    }

8. Lambda省略格式

可推導便可省略。

  • 省略規則:

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

    // 【練習2代碼:】
    // 省略前
        Arrays.sort(array,(Person o1, Person o2) ->{
          return o1.getAge()-o2.getAge();
        });
    // 省略後
        Arrays.sort(array,(o1, o2) ->return o1.getAge()-o2.getAge());

9. Lambda使用前提

Lambda的語法很是簡潔,徹底沒有面向對象複雜的束縛。可是使用時有幾個問題須要特別注意:

  1. 使用Lambda必須具備接口,且要求接口中有且僅有一個抽象方法。

    不管是JDK內置的 Runnable 、 Comparator 接口仍是自定義的接口,只有當接口中的抽象方法存在且惟一時,才能夠使用Lambda。

  2. 使用Lambda必須具備上下文推斷。

    也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda做爲該接口的實例。

備註:有且僅有一個抽象方法的接口,稱爲「函數式接口」。

相關文章
相關標籤/搜索