- lambada 表達式實質上是一個匿名方法,但該方法並非獨立執行,而是用於實現由函數式接口定義的唯一抽象方法
- 使用 lambda 表達式時,會創建實現了函數式接口的一個匿名類實例
- 可以將 lambda 表達式視為一個對象,可以將其作為參數傳遞
1. 函數式接口
函數式接口是僅含一個抽象方法的接口,但可以指定 Object 定義的任何公有方法。
- 以下是一個函數式接口:
1 @FunctionalInterface 2 public interface IFuntionSum<T extends Number> { 3 T sum(List<T> numbers); // 抽象方法 4 }
- 以下也是一個函數式接口:
1 @FunctionalInterface 2 public interface IFunctionMulti<T extends Number> { 3 void multi(List<T> numbers); // 抽象方法 4 5 boolean equals(Object obj); // Object中的方法 6 }
- 但如果改為以下形式,則不是函數式接口:
1 @FunctionalInterface 2 public interface IFunctionMulti<T extends Number> extends IFuntionSum<T> { 3 void multi(List<T> numbers); 4 5 @Override 6 boolean equals(Object obj); 7 } 8 // IFunctionMulti 接口繼承了 IFuntionSum 接口,此時 IFunctionMulti 包含了2個抽象方法
tip 1: 可以用 @FunctionalInterface 標識函數式接口,非強制要求,但有助於編譯器及時檢查接口是否滿足函數式接口定義
tip 2: 在 Java 8 之前,接口的所有方法都是抽象方法,在 Java 8 中新增了接口的默認方法
2. lambda 表達式
-
lambda 表達式的2種形式
包含單獨表達式 :parameters -> an expression1 list.forEach(item -> System.out.println(item));
包含代碼塊:parameters -> { expressions };
list.forEach(item -> { int numA = item.getNumA(); int numB = item.getNumB(); System.out.println(numA + numB); });
左側指定 lambda 表達式需要的參數,右側指定 lambda 方法體
-
上文提到,lambda 無法獨立執行,它必須是實現一個函數式接口的唯一抽象方法。
每個 lambda 表達式背后必定有一個函數式接口,該表達式實現的是這個函數式接口內部的唯一抽象方法。譬如以下 lambda 表達式:
list.forEach(item -> System.out.println(item));
我們看 ArrayList 中 foreach 方法:
@Override public void forEach(Consumer<? super E> action) { // 太長了,不看了~ }
其中 Consumer 是一個函數式接口:
@FunctionalInterface public interface Consumer<T> { void accept(T t); // lambda 表達式 item -> System.out.println(item) 實現了該方法 default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
Consumer 接口是 Java 8 中預先定義的函數式接口,java.util.function 包下都是些預定義的函數式接口
function 包下的部分接口使用了泛型,具有很強的通用性,在自定義函數式接口前,不妨去這個包下找找有沒有能用的 -
在執行 lambda 表達式時,會自動創建一個實現了目標函數式接口的類實例,該類實例是一個匿名內部類。
同樣以 list 的 foreach 方法為例:
@FunctionalInterface public interface Consumer<T> { void accept(T t); // lambda 表達式 item -> System.out.println(item) 實現了該方法 default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
用 Java VisualVM 追蹤代碼運行過程中的堆內存,發現會生成以下實例:
-
同樣,由於 lambda 表達式在執行時會生成目標函數式接口的類實例,因此我們可以做以下操作:
// 有以下函數式接口 @FunctionalInterface public interface IFuntionSum<T extends Number> { T sum(List<T> numbers); } // 將一個lambda表達式賦值給函數式接口引用(類型須兼容) IFuntionSum<Long> function = list -> { Long sum = 0L; for (Long item : list) { sum += item; } return sum; }; function.sum(Arrays.asList(1L, 2L)); // 執行結果為3L
在開發過程中,我們可以將 lambda 表達式等同於一個對象使用,對其聲明、引用、傳遞。
-
匿名內部類 和 lambda 表達式匿名內部類的命名規則
內部類的命名規則:外部類名 + $ + 內部類名
匿名類的命名規則:外部類名 + $ + (1, 2, 3,第幾個匿名類就顯示幾)
lambada 匿名內部類的命名規則:外部類名 + $$ + Lambda + $ + (1, 2, 3,第幾個lambda表達式就顯示幾)假設外部類中用到了2個lambda 表達式,則生成的2個匿名內部類的命名分別為 :
外部類名$$Lambda$1 和 外部類名$$Lambda$2
3. lambda 表達式規約
-
lambda 表達式的參數可以通過上下文推斷,如果需要顯示聲明一個參數的類型,則必須為所有的參數聲明類型。
@FunctionalInterface public interface IFunctionMod { boolean (int n, int d); } IFunctionMod function = (n, d) -> (n % d) == 0 // 合理,n 和 d 的類型通過上下文推斷 IFunctionMod function = (int n, int d) -> (n % d) == 0 // 合理,指定 n 和 d 的類型 IFunctionMod function = (int n, d) -> (n % d) == 0 // 不合理,須顯示聲明所有參數類型
-
lambda 表達式中拋出的異常需要與目標函數式接口的抽象方法拋出的異常類型兼容:
以下是合理的:@FunctionalInterface public interface IFunctionMod { boolean (int n, int d) throw Exception; } IFunctionMod function = (n, d) -> { if (d == 0) { // IOException是EXception 的子類,通過類型轉換,IOException 可轉換為 Exception throw new IOException("test"); } return n % d == 0; };
如果反一下,就不行了:
@FunctionalInterface public interface IFunctionMod { boolean (int n, int d) throw IOException; } IFunctionMod function = (n, d) -> { if (d == 0) { // 父類不能通過自動類型轉換轉為子類,lambda 表達式拋出的異常類型與抽象方法拋出的異常類型不兼容 throw new Exception("test"); } return n % d == 0; };
-
lambda 表達式中參數類型需要與目標函數式接口中抽象方法的參數類型兼容。
tip :從接口與實現的角度,可以很容易理解拋出異常兼容 和 參數類型兼容 這2點。
4. 方法引用
可以引用已有方法構造 lambda 表達式,這里給一個例子,不做詳細解釋:
list.forEach(System.out::print)
原文作者:EricAlpha:https://www.jianshu.com/p/613a6118e2e0