一文看懂 Java8 的 Lambda表達式!

  • 2020 年 4 月 10 日
  • 筆記

IT領域的技術日新月異,Java14問世了,但是對於國內的許多程序員來說,連Java8都還沒有真正掌握。

今天,我們就來溫習一下Java8的特性之一,Lambda表達式。

Lambda表達式的前世–匿名類

以往,使用單一抽象方法的接口被用作函數類型。它們的實例表示函數(functions)或行動(actions)。自從 JDK 1.1 於 1997 年發佈以來,創建函數對象的主要手段就是匿名類。

匿名類,通俗地講,就是沒有類名,直接通過new關鍵字創建這個類的實例。下面是匿名類的一個例子:

java.util包中的Comparator接口

public interface Comparator<T> {    int compare(T o1, T o2);  }  

使用匿名類創建排序的比較方法(強制排序順序):

Collections.sort(words, new Comparator<String>() {    public int compare(String s1, String s2) {        return Integer.compare(s1.length(), s2.length());   }  });  

匿名類適用於需要函數對象的經典面向對象設計模式,特別是策略模式,上面的匿名類是排序字符串的具體策略。然而,匿名類確實過於冗長。

Lambda表達式的今生

在 Java 8 中,語言形式化了這樣的概念,即使用單個抽象方法的接口是特別的,應該得到特別的對待。這些接口現在稱為函數式接口,並且該語言允許你使用lambda 表達式或簡稱 lambda 來創建這些接口的實例。Lambdas 在功能上與匿名類相似,但更為簡潔。下面的代碼使用 lambdas 替換上面的匿名類。清晰明了

Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));  

你告訴我還沒理解Lambda表達式?

那我們再來一個簡單的例子:

首先我們定義一個Learn接口

@FunctionalInterface  public interface Learn{    void study();  }  

為了獲取 Learn接口的實現對象,可以為該接口定義一個實現類 StudyDemo

public class StudyDemo implements Learn{    @Override    public void study() {      System.out.println("好好學習,天天向上");    }  }  

然後創建該實現類的對象調用study方法:

  Learn s = new StudyDemo ();    s.study();//運行結果-->好好學習,天天向上  

如果StudyDemo 類只是為了實現 Learn接口而存在的,且僅被使用了一次,所以使用匿名內部類來簡化這一操作:

public static void main(String[] args) {    studyInterface s = new studyInterface () {    @Override    public void study() {      System.out.println("好好學習,天天向上");      }    };    s.study();//運行結果-->好好學習,天天向上    }  

顯然,使用匿名內部類還不夠簡潔,所以我們用lambda表達式來優化:

因為Learn接口中只有一個方法,編譯器使用稱為類型推斷的過程從上下文中推導出這些類型和方法,所以我們可以省去study的方法名和new Learn,並加上箭頭 ->

public static void main(String[] args) {    Learn s = ()->{      System.out.println("好好學習,天天向上");      }    };    s.study();//運行結果-->好好學習,天天向上    }  

如果像上面一樣lambda表達式只有一行代碼,我們可以進一步優化:

public static void main(String[] args) {    Learn s = ()-> System.out.println("好好學習,天天向上");    s.study(); //運行結果-->好好學習,天天向上    }  

一行代碼對於 lambda 說是理想的,三行代碼是合理的最大值。如果違反這一規定,可能會嚴重損害程序的可讀性。如果一個 lambda 很長或很難閱讀,要麼找到一種方法來簡化它或重構你的程序來消除它。

至此為止,相信你已經對lambda有所了解,我們再來幾個例子加深理解

上面我們舉的例子中,study()方法既沒有參數也沒有返回值,如果有參數和返回值又該怎麼辦呢?

首先定義一個接口中的唯一抽象方法帶參數的情況:

interface Learn1{      void study(int a,int b);  }  

再看看它的匿名內部類和lambda表達式寫法:

public static void main(String[] args) {    //先定義一個接口的引用    Learn1 learn;         //匿名內部類寫法      learn = new Learn1() {          @Override          public void study(int a,int b) {              System.out.println("好好學習x"(a+b));          }      };      learn.study(1,2);//運行結果-->好好學習x2          //lambda表達式寫法,省略接口和方法名,      //方法的參數類型可以推導出來,也可以省略      learn = (a,b)->{          System.out.println("好好學習x"(a+b));      };      learn.study(3,4);//運行結果-->好好學習x12  

如果方法中只有一個參數,還可以省略小括號,Lambda表達式中只有一條語句可以省略大括號

 learn = e-> System.out.println("好好學習"+e);    learn.study(5);  

再來看看有返回值的情況,再回過頭來看java.util包中的Comparator接口:

public interface Comparator<T> {    int compare(T o1, T o2);  }  

先定義一個學生類:

public class Student {      private String name;      private int age;        public Student(String ,int age){        this.name = name;        this.age = age;      }      public int getAge(){        return this.age;  }  

接下來我們對數組中的Student對象,使用Arrays的sort方法通過年齡進行升序排序

public static void main(String[] args) {    Student[] array = {           new Student("張三", 18),           new Student("李四", 20),           new Student("王五", 19) };     //匿名內部類寫法      Comparator<Student > compare = new Comparator<Student >() {              @Override              public int compare(Student s1, Student s2) {                  return s1.getAge() ‐ s2.getAge();              }          };          //調用              Arrays.sort(array, compare);          //lambda表達式寫法,去掉接口名和方法名      Comparator<Student > compare = (Student s1, Student s2)->{                  return s1.getAge() ‐ s2.getAge();              }          };          //調用          Arrays.sort(array, compare);          //省略寫法,去掉參數類型,只保留返回值       Comparator<Student > compare = (s1,s2)-> s1.getAge() ‐ s2.getAge();    

總結

Lambda表達式的語法非常簡潔,但是使用時有幾個問題需要特別注意:

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

2. 使用Lambda必須具有上下文推斷。也就是方法的參數或局部變量類型必須為Lambda對應的接口類型,才能使用Lambda作為該接口的實例。

Lambda標準形式

(參數類型 參數名稱) ‐> { 代碼語句 }

說明:

1. 小括號內:沒有參數就留空(),多個參數就用逗號分隔。

2. -> 是新引入的語法格式,代表指向動作。

3. 大括號內的語法與傳統方法體要求基本一致。

Lambda的省略:凡是可以根據上下文推導得知的信息,都可以省略

在Lambda表達式標準形式的基礎上:

1. 小括號內參數的類型可以省略;

2. 如果小括號內只有一個參數,則小括號可以省略;

3. 如果大括號內只有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。

綜上所述,從 Java 8 開始,lambda 是迄今為止表示小函數對象的最佳方式。除非必須創建非函數式接口類型的實例,否則不要使用匿名類作為函數對象。

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