閱讀項目代碼時,尤爲是閱讀一些源碼時,常常會遇到 Lambda 表達式。對此以前看過相關文章,可是停留在模模糊糊的印象上。今天趁着有時間,經過一些 demo 示例,梳理一下它的用法,以備後期遺忘的時候快速查詢它的用法!java
Lambda 表達式是 Java 8 的重要更新,它支持將代碼塊做爲方法參數、容許使用更簡潔的代碼來建立只有一個抽象方法的接口的實例。python
描述中提到的接口稱爲函數式接口git
Lambda 表達式的主要做用就是能夠用於簡化建立匿名內部類對象,Lambda 表達式的代碼塊將會用於實現抽象方法的方法體,Lambda 表達式就至關於一個匿名方法。數組
Lambda 表達式由三部分組成:app
->
):經過英文畫線和大於符號組成;interface Eatable { void taste(); } interface Flyable { void fly(String weather); } interface Addable { int add(int a, int b); } public class LambdaQs { // 調用該方法須要傳入一個 Eatable 類型的對象 public void eat(Eatable e) { System.out.println(e); e.taste(); } // 調用該方法須要傳入 Flyable 類型的對象 public void drive(Flyable f) { System.out.println("我正在駕駛:" + f); f.fly("「夏日晴天」"); } // 調用該方法須要 Addable 類型的對象 public void calc(Addable add) { System.out.println("5 + 3 = " + add.add(5, 3)); } public static void main(String[] args) { LambdaQs lq = new LambdaQs(); // Lambda 表達式的代碼塊只有一句,所以省略了花括號 lq.eat(() -> System.out.println("雪糕的味道不錯!")); // Lambda 表達式的形參只有一個參數,所以省略了圓括號 lq.drive(weather -> { // 對接口中抽象方法 fly 的重寫 System.out.println("今每天氣是:" + weather); System.out.println("飛機平穩飛行!"); }); // Lambda 表達式只有一條語句,即便該表達式須要返回值,也能夠省略 return lq.calc((a, b) -> a + b); // 若是不用 Lambda 表達式,就須要以下匿名類的方式去重寫抽象方法 lq.calc(new Addable() { @Override public int add(int a, int b) { return a + b; } }); } }
輸出結果:ide
oop.lambda.LambdaQs$$Lambda$1/1607521710@7ef20235 雪糕的味道不錯! 我正在駕駛:oop.lambda.LambdaQs$$Lambda$2/1329552164@15aeb7ab 今每天氣是:「夏日晴天」 飛機平穩飛行! 5 + 3 = 8 5 + 3 = 8
以上示例能夠說明,Lambda 表達式實際上能夠被當作一個具體的對象。函數
Lambda 表達式的類型,也被稱爲「目標類型(target type
)」。Lambda 表達式的目標類型必須是「函數式接口(functional interface
)」。函數式接口表明只包含一個抽象方法的接口。函數式接口能夠包含多個默認方法、類方法,但僅能聲明一個抽象方法。oop
查詢 Java 8 的 API 文檔,能夠發現大量的函數式接口,例如:Runnable、ActionListener 等接口都是函數式接口。code
Java 8 專門爲函數式接口提供了
@FunctionalInterface
註解。該註解就是用於告訴編譯器校驗接口必須是函數式接口,不然就報錯。對象
因爲 Lambda 表達式的結果就是被當作對象/實例,所以,可使用 Lambda 表達式進行賦值,示例:
Runnable r = () -> { for (int i = 0; i < 100; i++) { System.out.println(i); } };
咱們看一下 Runnable 接口的定義:
@FunctionalInterface public interface Runnable { public abstract void run(); }
看一個錯誤示例:
Object obj = () -> { for (int i = 0; i < 100; i++) { System.out.println(i); } };
上面這段代碼會報錯:Target type of a lambda conversion must be an interface
。Lambda 表達式的目標類型必須是明確的函數式接口!將 Lambda 表達式賦值給 Object 類型的變量,編譯器只能推斷出它的表達類型爲 Object,而 Object 並非函數式接口,所以就報錯了!
爲了保證 Lambda 表達式的目標類型是明確的函數式接口,有以下三種常見方式:
將上面出錯的代碼能夠進行以下的改寫:
Object obj1 = (Runnable)() -> { for (int i = 0; i < 100; i++) { System.out.println(i); } };
綜上,Lambda 表達式的本質很簡單,就是使用簡單的語法來建立函數式接口的實例,避免匿名內部類的繁瑣。
若是 Lambda 表達式的代碼塊只有一條代碼,還能夠在代碼中使用方法引用和構造器引用。
方法引用和構造器引用的好處是使 Lambda 表達式的代碼塊更加簡潔。方法引用和構造器引用都須要使用兩個英文冒號 ::
。
種類 | 示例 | 說明 | 對應的 Lambda 表達式 |
---|---|---|---|
引用類方法 | 類名::類方法 | 函數式接口中被實現的方法的所有參數傳給該類方法做爲參數 | (a,b,...) -> 類名.類方法(a,b,...) |
引用特定對象的實例方法 | 特定對象::實例方法 | 函數式接口中被實現的方法的所有參數傳給該方法做爲參數 | (a,b,...) -> 特定對象.實例方法(a,b,...) |
引用某類對象的實例方法 | 類名::實例方法 | 函數式接口中被實現的方法的第一個參數做爲調用者,後面的參數所有傳給該方法做爲參數 | (a,b,...)->a.實例方法(b,...) |
引用構造器 | 類名::new | 函數式接口中被實現方法的所有參數傳給該構造器做爲參數 | (a,b,...)->new 類名(a,b,...) |
@FunctionalInterface interface Converter { Integer convert(String from); } @FunctionalInterface interface MyTest { String test(String a, int b, int c); } @FunctionalInterface interface YourTest { // 抽象方法負責根據 String 參數生成一個 JFrame 返回值 JFrame win(String title); } public class LambdaRef { public static void main(String[] args) { // 1 引用類方法 // 下面使用 Lambda 表達式建立 Converter 對象 Converter converter1 = from -> Integer.valueOf(from); Integer val = converter1.convert("99"); // 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 Converter converter2 = Integer::valueOf; Integer val2 = converter2.convert("100"); // 2 引用特定對象的實例方法 // 使用 Lmabda 表達式建立 Converter 對象 Converter converter3 = from -> "hello michael翔".indexOf(from); // 調用 "hello michael翔"的indexOf()實例方法 // 函數式接口中被實現的所有參數傳給該方法做爲參數 Converter converter4 = "hello michael翔"::indexOf; // 3 引用某類對象的實例方法 // 使用 Lambda 表達式建立 MyTest 對象 MyTest mt = (a, b, c) -> a.substring(b, c); String str = mt.test("Hello World, Hello Michael翔", 2,9); // 上面 Lambda 表達式只有一行,所以可使用以下引用進行替換 // 函數式接口中被實現方法的第一個參數做爲調用者 // 後面的參數所有傳給該方法做爲參數 MyTest str2 = String::substring; // 4 引用構造器 // 使用 Lambda 表達式建立 YourTest 對象 YourTest yt = a -> new JFrame(a); JFrame jf = yt.win("窗口"); // 使用構造器引用進行替換 // 函數式接口中被實現方法的所有參數傳給該構造器做爲參數 YourTest yt2 = JFrame::new; JFrame jf2 = yt.win("窗口2"); } }
Lambda 表達式與匿名內部類存在以下相同點:
effectively final
的局部變量,以及外部類的成員變量(包括示例變量和類變量);Lambda 表達式與匿名內部類的區別:
@FunctionalInterface interface Converter { Integer convert(String from); } @FunctionalInterface interface MyTest { String test(String a, int b, int c); } @FunctionalInterface interface YourTest { // 抽象方法負責根據 String 參數生成一個 JFrame 返回值 JFrame win(String title); } public class LambdaRef { public static void main(String[] args) { // 1 引用類方法 // 下面使用 Lambda 表達式建立 Converter 對象 Converter converter1 = from -> Integer.valueOf(from); Integer val = converter1.convert("99"); // 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 Converter converter2 = Integer::valueOf; Integer val2 = converter2.convert("100"); // 2 引用特定對象的實例方法 // 使用 Lmabda 表達式建立 Converter 對象 Converter converter3 = from -> "hello michael翔".indexOf(from); // 調用 "hello michael翔"的indexOf()實例方法 // 函數式接口中被實現的所有參數傳給該方法做爲參數 Converter converter4 = "hello michael翔"::indexOf; // 3 引用某類對象的實例方法 // 使用 Lambda 表達式建立 MyTest 對象 MyTest mt = (a, b, c) -> a.substring(b, c); String str = mt.test("Hello World, Hello Michael翔", 2,9); // 上面 Lambda 表達式只有一行,所以可使用以下引用進行替換 // 函數式接口中被實現方法的第一個參數做爲調用者 // 後面的參數所有傳給該方法做爲參數 MyTest str2 = String::substring; // 4 引用構造器 // 使用 Lambda 表達式建立 YourTest 對象 YourTest yt = a -> new JFrame(a); JFrame jf = yt.win("窗口"); // 使用構造器引用進行替換 // 函數式接口中被實現方法的所有參數傳給該構造器做爲參數 YourTest yt2 = JFrame::new; JFrame jf2 = yt.win("窗口2"); } }
Arrays 類的有些方法須要 Comparator、XxxOperator、XxxFunction 等接口的實例,這些接口都是函數式接口。所以,可使用 Lambda 表達式來調用 Arrays 的方法。
public class LambdaArrays { public static void main(String[] args) { String[] arr1 = new String[]{"java", "python", "rust", "go"}; Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length()); System.out.println(Arrays.toString(arr1)); int[] arr2 = {3, -4, 25, 16, 30, 18}; // left 表明數組中前一個索引處的元素,計算第一個元素時,left 爲 1; // right 表明數組中的當前索引處的元素 Arrays.parallelPrefix(arr2, (left, right) -> left * right); System.out.println(Arrays.toString(arr2)); long[] arr3 = new long[5]; // a 表明正在計算的元素索引 Arrays.parallelSetAll(arr3, a -> a * 5); System.out.println(Arrays.toString(arr3)); // 等價於用匿名內部類重寫 applyAsLong 抽象方法 Arrays.parallelSetAll(arr3, new IntToLongFunction() { @Override public long applyAsLong(int value) { return value * 5; } }); System.out.println(Arrays.toString(arr3)); } }
輸出:
[go, java, rust, python] [3, -12, -300, -4800, -144000, -2592000] [0, 5, 10, 15, 20] [0, 5, 10, 15, 20]
由於這些要出入 Comparator、XxxOperator、XxxFunction 等接口的實例每每都是一次性的,使用 Lambda 表達式也不用考慮重用等,反而讓程序更加簡潔了。
本文主要參考的是 《瘋狂 Java 講義第 5 版》的第 6 章的面向對象下,經過實際的示例 demo 應該能夠將 Lambda 的經常使用場景和用法掌握了。這樣,看項目代碼或者源碼的話,會更加易於理解!基本功紮實,才能走得更快!