Java8新特性之Lambda

  • 2019 年 10 月 22 日
  • 筆記

為什麼要Lambda

Java8應該是目前最大的一次更新了,更新後我們迎來了很多新特性,其中便包括Lambda表達式,函數式編程的思想正式進入Java,讓我們看一個經典案例。

例1 按照兩個人的年齡排序的功能

採用匿名內部類已經算簡介了,如果專門用一個類去實現Comparator再new出來就更煩了,過去的寫法:

// 已經創建好了三個Person實例  List<Person> people = Arrays.asList(person1, person2, person3);    Collections.sort(people, new Comparator<Person>() {      @Override      public int compare(Person o1, Person o2) {          return o1.getAge().compareTo(o2.getAge());      }  });

Lambda版本寫法:

Collections.sort(people, (p1, p2) -> p1.getAge().compareTo(p2.getAge()));

還有更簡潔的方法引用寫法:

Collections.sort(people, Comparator.comparing(Person::getAge));

是不是真的短真的易讀,語法糖真的甜!已經9102年了,函數式編程被提到的越來越多,深諳照貓畫虎已經行不通了,而且函數式編程和設計模式的碰撞也很多,真的有必要了解下相關概念

哪裡用Lambda

函數式編程

函數式編程是一種思想,核心是行為參數化,把一段程式碼像值一樣傳遞給方法,傳入不同的程式碼實現不同的功能

這是不是很像策略模式以及模板模式?如例1所示,不需要大量的套路程式碼了,也不需要把程式碼寫到一個類中然後新建實例對象最後把實例對象傳遞

函數式介面

函數式介面就是只定義一個抽象方法的介面來表示行為,抽象方法不允許拋出受檢異常,Java8介面可以有default方法了,函數式介面是允許有default方法的

Lambda表達式看上去確實很有吸引力,我能在任何地方都使用么?答案是不能的,我們只能通過Lambda表達式把程式碼傳到函數式介面中,拿例1中的Comparator介面來看

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

Comparator介面只有compare一個抽象方法,Java8特意給它加了註解告訴我們這就是個函數式介面,其實也很好理解,我們沒有創建任何實例,只傳了一串程式碼,如果Comparator有兩個抽象方法,編譯器怎麼知道我們實現的是compare方法呢

怎麼用Lambda

上面都是說的函數式編程,那麼什麼是Lambda

Lambda就是匿名的行為參數化的一種語法實現,它沒有名稱,但它有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常列表

語法

  • (parameters) -> expression
    • 默認Return的,expression只能是一句程式碼
  • (parameters) -> { statements; }
    • 沒有默認Retrun,就相當於Comparator.compare( statements; ),statements可以是好多行

tips:如果statements很長,那麼我們就不應該用Lambda,而應該單獨實現一個方法,然後使用方法引用這樣可讀性更好,繼續例1

// 比如說在MyUtils類下寫個方法,故意加長  public static Integer sortPersonByName(Person person1, Person person2) {      Integer age1 = person1.getAge();      Integer age2 = person2.getAge();      return age1.compareTo(age2);  }    // 又用到了方法引用,我們可以把方法引用當作一種便於閱讀的語法糖,功能也是傳遞程式碼  Collections.sort(people, Comparator.comparing(MyUtils::sortPersonByAge));

憑什麼Lambda

在使用Lambda的時候我們沒有任何類型聲明就能工作這是怎麼做到的呢?

函數描述符

函數式介面的抽象方法的簽名基本上就是Lambda表達式的簽名。我們將這種抽象方法叫作
函數描述符

Comparator.compare的簽名

  • int compare(T o1, T o2);
  • 這個函數式介面的簽名就可以描述成需要兩個相同類型的變數,然後返回int

  • ( T, T ) -> int

Lambda的簽名

  • (p1, p2) -> p1.getAge().compareTo(p2.getAge())  // 這是易讀的寫法,我們也可以寫成方便說明  (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge())
  • 可以描述成需要兩個Person變數,然後compareTo方法返回int

  • (Person, Person) -> int

編譯器會做類型推斷和類型檢查,發現兩個簽名匹配,我們的Lambda表達式就可以順利執行了

tips:Lambda主體是語句表達式的時候(parameters) -> expression ,儘管expression返回可能不是void,但是也是兼容 T -> void 簽名的

進階

新增函數式介面

除了 Runnable,Comparator等常用函數式介面,為了推動函數式編程,Java8又在 java.util.function 包下為我們提供了大量好用的函數式,因為基本數據類型不能抽象成對象,所以可以看到有大量Double,Int,Long前綴的介面,我們只看比較核心的:

介面名 抽象方法 描述符
Predicate boolean test(T t) T -> boolean
BiPredicate<T, U> test(T t, U u) ( T, U ) -> boolean
Consumer void accept(T t) T -> void
BiConsumer<T, U> void accept(T t, U u) ( T, U ) -> void
Function<T, R> R apply(T t) T -> R
BiFunction<T, U, R> R apply(T t, U u) ( T, U ) -> R
Supplier T get() void -> T

複合Lambda

令人驚喜的是,java8提供的函數式介面還有許多好用的default方法,可以讓我們把多個Lambda複合起來,組成流水線。拿用的比較多的Function介面舉例,我們要寫一封郵件,我們關注的是信的內容

Function<String, String> writeEmailHeader = text -> "Hi ," + "n" + text;  Function<String, String> writeEmailText = text -> text + "n";  Function<String, String> writeEmailFooter = text -> text + "BRs" + "n" + "Chen";  Function<String, String> writeEmail = writeEmailHeader.andThen(writeEmailText).andThen(writeEmailFooter);    System.out.println(writeEmail.apply("I will take half day sick leave today"));    /**   * 輸入如下:   * Hi ,   * I will take half day sick leave today   * BRs   * Chen   */