一文看懂 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 是迄今為止表示小函數對象的最佳方式。除非必須創建非函數式介面類型的實例,否則不要使用匿名類作為函數對象。
備註:有且僅有一個抽象方法的介面,稱為「函數式介面」。