Java8新特性 - Lambda

JAVA一方發表於2020-02-16

為什麼要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, 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字首的介面,我們只看比較核心的:

介面名抽象方法描述符
Predicateboolean test(T t)T -> boolean
BiPredicate<T, U>test(T t, U u)( T, U ) -> boolean
Consumervoid 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
SupplierT get()void -> T

tips:Predicate這種對給定內容做判斷返回boolean 值,我們叫做謂詞

複合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
 * /複製程式碼


相關文章