寫在前面
lambda表示式與匿名內部類
無參的函式式介面
public static void createThreadWithAnonymousClass() { // Runnable 是介面名。我們通過匿名內部類的方式,構造了一個 Runnable 的例項。 Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("Thread is running"); } }); t.start(); }
使用匿名內部類的一個重要目的,就是為了減輕程式設計師的程式碼負擔,不需要額外再定義一個類,而且這個類是一個一次性的類,沒有太多的重用價值。但是,我們會發現,這個物件看起來也是多餘的,因為我們實際上並不是要傳入一個物件,而只是想傳入一個方法。
public static void createThreadWithLambda() { // 在Java 8中,Runnable 是一個函式式介面,因此我們可以使用 lambda 表示式來實現它。 Thread t = new Thread(() -> { System.out.println("Thread is running"); }); t.start(); }
帶參的函式式介面
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); .... }
假設一個場景:給定一個省份的拼音列表,需要對該列表中的省份進行排序,排序規則是字母長度最小的省份排在前面,如果兩個省份字母長度一樣,則按字母順序排序。
public static void sortProvincesWithAnonymousClass() { List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu", "Xizang", "Fujian", "Hunan", "Guangxi"); list.sort(new Comparator<String>() { @Override public int compare(String first, String second) { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; } }); list.forEach(s -> System.out.println(s)); }
public static void sortProvincesWithLambda() { List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu", "Xizang", "Fujian", "Hunan", "Guangxi"); // 下面的引數列表 first 和 second ,即方法 Comparator.compare 的引數列表 list.sort((first, second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }); list.forEach(s -> System.out.println(s)); }
注意到,帶引數的lambda表示式,甚至不需要宣告型別,因為編譯器可以通過上下文來推斷出引數的型別。當然,我們也可以顯式指定引數型別,尤其是在引數型別推斷失敗的時候:
(String first, String second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }
this關鍵字的作用域
public class ThisScopeExample { public static void main(String[] args) { ThisScopeExample example = new ThisScopeExample(); // 輸出 "I am Anonymous Class." example.runWithAnonymousClass(); // 輸出 "I am ThisScopeExample Class." example.runWithLambda(); } public void runWithAnonymousClass() { // 以匿名類的方式執行 run(new Runnable() { @Override public void run() { // this 是實現了介面 Runnable 的匿名內部類的例項 System.out.println(this); } @Override public String toString() { return "I am Anonymous Class."; } }); } public void runWithLambda() { // 以lambda表示式的方式執行 run(() -> { // this 是類 ThisScopeExample 的例項 System.out.println(this); }); } public void run(Runnable runnable) { runnable.run(); } @Override public String toString() { return "I am ThisScopeExample Class."; } }
lambda表示式的語法
(String first, String second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; }
上述是一個典型的而且完整的lambda表示式。
Supplier<Integer> supplier = () -> { return new Random().nextInt(100); }
對於上面的lambda表示式,可以發現它的方法體只有一個表示式,所以,它可以省略大括號,甚至return關鍵字也省略了,因為編譯器可以根據上下文推斷是否需要返回值:如果需要,那麼就返回該唯一表示式的返回值,如果不需要,則在該唯一表示式後直接return。例如:
// Supplier 是需要返回值的,所以下面的lambda表示式等同於: // () -> { return new Random().nextInt(100); } Supplier<Integer> supplier = () -> new Random().nextInt(100); // Runnable 是不需要返回值的,所以下面的lambda表示式等同於: // () -> { new Random().nextInt(100); return; } Runnable runnable = () -> new Random().nextInt(100);
如果編譯器可以推斷出lambda表示式的引數型別,則可以忽略其型別:
// 在這裡,編譯器可以推斷出 first 和 second 的型別是 String。 Comparator<String> comp = (first, second) -> { int lenDiff = first.length() - second.length(); return lenDiff == 0 ? first.compareTo(second) : lenDiff; };
如果lambda表示式只有一個引數,那麼引數列表中的小括號也可以省略掉:
// 這裡的 value ,等同於 (value) Consumer<String> consumer = value -> System.out.println(value);
與普通的函式不一樣,lambda表示式不需要指定返回型別,它總是由編譯器自行推斷出返回型別。如果推斷失敗,則預設為Object型別。
lambda表示式與閉包
public class ClosureExample { public static void main(String[] args) { // 平方 IntUnaryOperator square = getPowOperator(2); // 立方 IntUnaryOperator cube = getPowOperator(3); // 四次方 IntUnaryOperator fourthPower = getPowOperator(4); // 5的平方 System.out.println(square.applyAsInt(5)); // 5的立方 System.out.println(cube.applyAsInt(5)); // 5的四次方 System.out.println(fourthPower.applyAsInt(5)); } public static IntUnaryOperator getPowOperator(int exp) { return base -> { // 變數 exp 是 getPowOperator 的引數,屬於lambda 表示式定義時的自由變數, // 它的生命週期會延長到和返回的 lambda 表示式一樣長。 return (int) Math.pow(base, exp); }; } }
上述程式碼的輸出是:
public static IntUnaryOperator getPowOperator(int exp) { // 嘗試修改 exp 的值,但編譯器會在lambda表示式中報錯 exp++; return base -> { // 如果嘗試修改 exp 的值,會在此處報錯: // Error: 從lambda 表示式引用的本地變數必須是final變數或實際上的final變數 return (int) Math.pow(base, exp); }; }
但這種限制也是有限的,因為我們可以通過將變數宣告為一個陣列或一個類就可以修改其中的值。例如:
public static IntUnaryOperator getPowOperator(int[] exp) { // exp 是一個int陣列:exp = new int[1]; exp[0]++; return base -> { // 此時不會報錯,可以正常執行 return (int) Math.pow(base, exp[0]); }; }
結語
為方便大家在移動端瀏覽,已註冊微信公眾號【員說】,歡迎關注。第一時間更新技術文章,也會不定時分享圈內熱門動態和一線大廠內幕。
感謝您閱讀本篇文章,如果覺得本文對您有幫助,歡迎點選推薦和關注,您的支援是我最大的寫作動力。
文章歡迎轉載,但需在文章頁面明顯位置,給出作者和原文連結,否則保留追究法律責任的權利!
注意!應各位朋友的邀請,建立了一個技術交流群,(聊技術/看內幕/找內推/讀書分享等,拒絕水群,保證品質),可新增微訊號【yuanshuo824】,備註:交流,即可入群。