[TOC] Lambda表達式支持將代碼塊做爲方法的參數,Lambda表達式容許使用更加簡潔的代碼來建立一個只有一個抽象方法的接口(這種接口被稱爲函數式接口)的實例。 #1、Lambda表達式入門——爲了不匿名內部類的繁瑣 咱們前面介紹了Command表達式的例子: 定義一個處理數組元素的接口java
package one; public interface Command { //接口裏定義的Process方法用於封裝「處理行爲」 void process(int element); }
定義一個處理數組的類android
package two; import one.Command; public class ProcessArray { public void process(int[] target,Command cmd) { for(var t:target) { cmd.process(t); } } }
import one.Command; import two.ProcessArray; class CommandTest1 { public static void main(String[] args) { var pa=new ProcessArray(); int[] a={1,5,9,7}; pa.process(a,new Command(){ public void process(int element) { System.out.println("數組元素的平方:"+element*element); } }); } } ---------- 運行Java捕獲輸出窗 ---------- 數組元素的平方:1 數組元素的平方:25 數組元素的平方:81 數組元素的平方:49 輸出完成 (耗時 0 秒) - 正常終止
import one.Command; import two.ProcessArray; class CommandTest2 { public static void main(String[] args) { var pa=new ProcessArray(); int[] a={1,5,9,7}; pa.process(a,(int element)-> System.out.println("數組元素的平方:"+element*element)); } }
這段代碼代碼與建立匿名內部類時實現的process(int element)方法徹底相同,只是不須要new Xxx(){}的繁瑣形式,不須要指出重寫方法的名字,也不須要指出重寫方法的返回值類型,只須要給出重寫方法括號以及括號裏的形參列表便可。 ##三、Lambda語句的組成 Lambda表達式主要用於代替匿名內部類的繁瑣語法。它由三部分組成: 一、形參列表。形參列表容許是省略類型。若是形參列表只有一個參數,甚至連形參列表的圓括號也能夠省略。 二、箭頭(->) 三、代碼塊。若是代碼塊只有一條語句容許省略代碼塊的花括號;若是隻有一條return語句,甚至能夠省略return關鍵字。Lambda表達式須要返回值,而他的代碼塊僅有一條省略了return 語句,Lambda表達式會自動返回這條語句的值。 Lambda表達式的集中簡化形式:ios
interface Eatable { void taste();//public abstract } 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 test(Addable add) { System.out.println("3加5的和爲:"+add.add(3,5)); } public static void main(String[] args) { var lq=new LambdaQs(); //Lambda語句只有一條語句,能夠省略花括號 lq.eat(()->System.out.println("蘋果味道不錯!")); //Lambda表達式形參列表只有一個形參,能夠省略圓括號 lq.drive(weather->{ System.out.println("今每天氣是"+weather); System.out.println("直升機平穩飛行");}); //Lambda只有一條語句時,能夠省略花括號 //代碼塊只有一條語句,即便該表達式須要返回值,也能夠省略return關鍵字 lq.test((a,b)->{return (a+b);}); lq.test((a,b)->a+b); } } ---------- 運行Java捕獲輸出窗 ---------- LambdaQs$$Lambda$1/0x0000000801201040@72ea2f77 蘋果味道不錯! 我正在駕駛:LambdaQs$$Lambda$2/0x0000000801201840@eed1f14 今每天氣是[碧空如洗的晴天] 直升機平穩飛行 3加5的和爲:8 3加5的和爲:8 輸出完成 (耗時 0 秒) - 正常終止
lq.eat()使用不帶形參列表的匿名方法,因爲該Lambda表達式只有一條代碼,所以能夠省略花括號; lq.drive()的Lambda表達式的形參列表只有一個形參,所以省略了形參列表的圓括號; lq.test()的Lambda表達式的代碼塊只有一行語句,這行語句的返回值做爲該代碼塊的返回值。編程
Lambda表達式的類型,也成爲「目標類型(target type)」,Lambda表達式的目標類型必須是「函數式接口(functional interface)」。<font color=red>函數式接口表明只包含一個一個抽象方法的接口。函數式接口能夠包含多個默認方法、類方法,但只能聲明一個抽象方法。</font>數組
若是採用匿名內部類語法來建立函數式接口,且只須要實現一個抽象方法,在這種狀況下,便可採用Lambda表達式來建立對象,該表示建立出來的對象的目標類型就是函數式接口。 注: Java 8專門爲函數式接口提供了@FunctionalInterface註解,該註解用於方法在接口定義前面,該註解對程序功能沒有任何影響,它用於告訴編譯器執行更嚴格的檢查——檢查該接口必須是函數式接口,不然編譯就會出錯。 下面程序使用匿名內部類:函數式編程
/*@FunctionalInterface *A 不是函數接口 * 在 接口 A 中找到多個非覆蓋抽象方法 */ interface A { public void test1(); public void test2(); default void test3()//接口中的默認方法 { System.out.println("接口A中的默認方法"); } } public class 適用匿名內部類 { public void test(A a) { System.out.println("接口A含有兩個抽象方法和一個默認方法,此時適合用匿名內部類"); a.test1(); a.test2(); a.test3(); } public static void main(String[] args) { var p=new 適用匿名內部類(); p.test(new A() { public void test1() { System.out.println("接口中的抽象方法1"); } public void test2() { System.out.println("接口中的抽象方法2"); } }); } } 接口A含有兩個抽象方法和一個默認方法,此時適合用匿名內部類 接口中的抽象方法1 接口中的抽象方法2 接口A中的默認方法
下面定義的接口B只有一個抽象方法,是函數式接口,此時適合用Lambda表達式:函數
@FunctionalInterface interface B { void test1(String msg);//抽象方法,默認public abstract default void test2()//接口中的默認方法 { System.out.println("接口A中的默認方法"); } } public class LambdaFor { public void test(B b) { System.out.println("接口A含有1個抽象方法和一個默認方法,是函數式接口"); b.test1("函數式接口A中的抽象方法"); b.test2(); } public static void main(String[] args) { var p=new LambdaFor(); p.test((msg)-> System.out.println(msg)); } } ---------- 運行Java捕獲輸出窗 ---------- 接口A含有1個抽象方法和一個默認方法,是函數式接口 函數式接口A中的抽象方法 接口A中的默認方法 輸出完成 (耗時 0 秒) - 正常終止
用於Lambda表達式的結果就是被當成對象,所以程序中徹底可使用Lambda表達式進行賦值。咱們知道接口不能建立實例,接口中只能定義常量,所以接口不存在構造器和初始化塊。接口不能建立實例,可是經過Lambda表達式咱們能夠建立一個「目標類型」並把它賦值給函數式接口的對象。 例如:spa
@FunctionalInterface interface Runnable { void printNum(); } public class RunnableTest { public static void main(String[] args) { //Runnable接口中只包含一個無參數的構造器方法 //Lambda表達式表明的匿名方法實現了Runnable接口中惟一的無參數方法 //所以下面的方法建立了一個Runnable的對象 Runnable r=()->{ for(int i=0;i<10;i++) System.out.print(" "+i); }; r.printNum(); } } ---------- 運行Java捕獲輸出窗 ---------- 0 1 2 3 4 5 6 7 8 9 輸出完成 (耗時 0 秒) - 正常終止
<font color=red>Lambda表達式實現的匿名方法——所以它只能實現特定函數式接口中惟一方法。這意味着Lambda表達式有兩個限制: 一、Lambda表達式的目標類型必須是明確的函數式接口。 二、Lambda表達式只能爲函數式接口建立對象。Lambda表達式只能實現一個方法,所以他只能爲只有一個抽象方法的接口(函數式接口)建立對象。</font> 關於第一點限制舉例:code
@FunctionalInterface interface A { void test(); } class LambdaLimit1 { public static void main(String[] args) { //Object a=()->{System.out.println("This is a test!");}; //上面代碼將報錯: 不兼容的類型: Object 不是函數接口 //Lambda表達式的目標類型必須是明確的函數式接口 A a=()->{System.out.println("This is a test!");}; a.test();//This is a test! } }
從錯誤信息能夠看出,Lambda表達式的目標類型必須是明確的函數式接口。上述表達式將Lambda表達式賦給Object變量,編譯器只能肯定該表達式的類型爲Object,而Object並非函數式接口。 爲了保證Lambda表達式的目標類型是一個明確的函數式接口,常見有三種方式: 一、將Lambda表達式賦值給函數式接口的變量;orm
//參考上面的完整程序 A a=()->{System.out.println("This is a test!");};
二、將Lambda表達式做爲函數接口類型的參數傳給某個方法。
interface A { void test(String msg); } public class ATest { public static void med(A a) { System.out.println("主類的非靜態方法"); a.test("我是傳奇"); } public static void main(String[] args) { ATest.med((msg)->System.out.println(msg)); } } ---------- 運行Java捕獲輸出窗 ---------- 主類的非靜態方法 我是傳奇 輸出完成 (耗時 0 秒) - 正常終止
三、使用函數式接口類型對Lambda表達式進行強制轉換。
Object a=(A)()->{System.out.println("This is a test!");};
對與var聲明變量,程序可使用Lambda表達式進行賦值。但因爲var表明須要由編譯器推斷的類型,所以使用Lambda表達式對var表達式定義的變量進行賦值時,必須指明Lambda表達式的目標類型。 例如:
var a=(A)()->{System.out.println("This is a test!");};
若是程序須要對Lambda表達式的形參列表添加註解,此時就不能省略Lambda表達式的形參類型——由於註解只能放在形參類型以前。在Java 11以前,程序必須嚴格聲明Lambda表達式中的每一個形參類型,但實際上編譯器徹底能夠推斷出lambda表達式中每一個形參的類型。
例如:下面程序定義了一個Predator接口,該接口中的prey方法的形參使用了@NotNull註解修飾:
@interface NotNull{} interface Predator { void prey(@NotNull String animal); }
接下來程序打算使用Lambda表達式來實現一個Predator對象。若是Lambda表達式不須要對animal形參使用@NotNull註解,則徹底能夠省略animal形參註解;但若是但願爲animal形參註解,則必須爲形參聲明類型,此時可直接使用var來聲明形參類型。
@interface NotNull{} interface Predator { void prey(@NotNull String animal); } public class PredatorTest { public static void main(String[] args) { //使用var聲明lambda表達式的形參類型 //這樣便可爲Lambda表達式的形參添加註解 Predator p=(@NotNull var animal)->System.out.println("老鷹在抓"+animal); p.prey("小雞"); } } //老鷹在抓小雞
#4、方法引用和構造器引用 Lambda表達式的方法引用和構造器引用都須要兩個英文冒號::。Lambda表達式支持以下幾種引用方式:
種類 | 示例 | 說明 | 對應的Lambda表達式 |
---|---|---|---|
引用類方法 | 類名::類方法名 | 函數式接口中被實現的方法的參數所有傳給該類方法做爲參數 | (a,b...)->類名.類方法(a,b...) |
引用特定對象的實例方法 | 特定對象::示例方法名 | 函數式接口中被實現的方法的參數所有傳給該實例方法做爲參數 | (a,b...)->特定對象.實例方法(a,b...) |
引用某類對象的實例方法 | 類名::實例方法名 | 函數式接口中被實現的方法的第一個參數做爲調用者,後面的參數傳給該方法做爲參數 | (a,b,c...)->a.實例方法(b,c...) |
引用構造器 | 類名::new | 函數式接口中被實現的方法的所有參數傳給該構造器做爲參數 | (a,b...)->new 類名(a,b...) |
##4.1 引用類方法 |
@FunctionalInterface interface Converter { Integer convert(String form); } public class ConverterTest { public static void main(String[] args) { //Lambda表達式只有一條語句,能夠省略1花括號:Lambda表達式會把這條代碼的值做爲返回值 Converter c=(form)->Integer.parseInt(form); System.out.println(c.convert("185")); //下面經過引用類方法來實現相同的功能 Converter cPlus=Integer::valueOf; System.out.println(cPlus.convert("140")); } }
##4.2 引用特定對象的實例方法
@FunctionalInterface interface Converter { Integer convert(String form); } public class ConverterTest1 { public static void main(String[] args) { //先使用Lambda表達式來建立一個Converter對象 Converter c=form->"fkit.org".indexOf(form);//代碼塊只有一條語句,所以Lambda表達式會把這條代碼的值做爲返回值 System.out.println(c.convert("it"));//輸出2 //引用特定對象的特定方法 "fkit.org"是一個String對象 Converter c1="fkit.org"::indexOf; System.out.println(c1.convert("org"));//輸出5 } }
對於上面的示例方法引用,也就是說,調用"fkit.org"對象的indexOf()實例方法來實現Converter函數式接口中惟一的抽象方法,當調用Converter接口中的惟一抽象的方法時,調用參數會傳給"fkit.org"對象的indexOf()實例方法。 ##4.3 引用某類對象的實例方法 先介紹一個函數:public String substring(int beginIndex, int endIndex)返回字符串索引範圍[beginIndex,endIndex)的子字符串。
@FunctionalInterface interface MyTest { String test(String a,int b, int c); } class substringTest { public static void main(String[] args) { MyTest m=(a,b,c)->a.substring(b,c); System.out.println(m.test("fkjava",1,5)); //引用某類對象的實例方法 MyTest mPlus=String::substring; System.out.println(mPlus.test("hello world",2,7));//至關於"hello world".substring(2,7) } } ---------- 運行Java捕獲輸出窗 ---------- kjav llo w 輸出完成 (耗時 0 秒) - 正常終止
##4.4 引用構造器 JFrame屏幕上window的對象,可以最大化、最小化、關閉
import java.awt.*; import javax.swing.*; @FunctionalInterface interface YourTest { JFrame win(String title); } public class MethodRefer { public static void main(String[] args) { // 下面代碼使用Lambda表達式建立YourTest對象 // YourTest yt = (String a) -> new JFrame(a); // 構造器引用代替Lambda表達式。 // 函數式接口中被實現方法的所有參數傳給該構造器做爲參數。 YourTest yt = JFrame::new; JFrame jf = yt.win("個人窗口"); System.out.println(jf); } }
#5、Lambda表達式和匿名內部類的聯繫和區別 Lambda表達式與匿名內部類之間存在以下相同點: 一、Lambda表達式與匿名內部類同樣,均可以直接訪問"effectively final"的局部變量,以及外部類的成員變量,包括實例變量和類變量。 二、Lambda表達式建立的對象與匿名內部類生成的對象同樣,均可以直接從接口中繼承的默認方法。
@FunctionalInterface interface Displayable { void display(); default int add(int a,int b) { return a+b; } } public class LambdaAndInner { private int age=12; private static String name="fkit.org"; public void test() { var book="瘋狂Java講義"; Displayable dis=()->{ //訪問"effictively final"的局部變量 System.out.println("book局部變量爲:"+book); //訪問外部類的實例變量和類變量 System.out.println("外部類的age實例變量:"+age); System.out.println("外部類的name類變量:"+name); }; dis.display(); //調用方對從接口繼承add()方法 System.out.println(dis.add(3,5)); } public static void main(String[] args) { var lambda=new LambdaAndInner(); lambda.test(); } } ---------- 運行Java捕獲輸出窗 ---------- book局部變量爲:瘋狂Java講義 外部類的age實例變量:12 外部類的name類變量:fkit.org 8 輸出完成 (耗時 0 秒) - 正常終止
與匿名函數類似的是,因爲Lambda表達式訪問了book局部變量,所以該局部變量至關於有一個隱式的final修飾,所以一樣不容許對book局部變量從新賦值。當程序使用了Lambda表達式建立了Displayable對象以後,該對象不只可調用接口的抽象方法,也能夠調用接口中的默認方法,所以一樣不容許對book局部變量從新賦值。 </font color=red>Lambda表達式與匿名內部類的區別: 一、匿名內部類能夠爲內部類能夠爲任意接口建立實例;但Lambda表達式只能爲函數式建立實例。 二、匿名內部類能夠爲抽象類乃至普通類建立實例;但Lambda表達式只能爲函數式接口建立實例。 三、匿名內部類是實現抽象方法的方法體容許調用接口中定義的默認方法;但Lambda表達式的代碼塊不容許不容許調用接口中的默認方法。</font>
#6、使用Lambda表達式調用Arrays的類方法 Arrays類的有些方法須要Comparator、XxxOperator、XxxFunction等接口的實例,這些接口都是函數式編程,所以可使用Lambda表達式來調用Arrays的方法。
import java.util.Arrays; public class LambdaArrays { public static void main(String[] args) { var arr1 = new String[] {"java", "fkava", "fkit", "ios", "android"}; Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length()); //這行Lambda表達式的目標類型是Comparator,該Comparator指定判斷字符串大小的標準:字符串越長,認爲該字符串越大 System.out.println(Arrays.toString(arr1)); var arr2 = new int[] {3, -4, 25, 16, 30, 18}; // left表明數組中前一個所索引處的元素,計算第一個元素時,left爲1 // right表明數組中當前索引處的元素 Arrays.parallelPrefix(arr2, (left, right)-> left * right); //這行Lambda表達式的目標類型是IntBinaryOperator,該對象將會根據先後兩個元素來計算當前元素 System.out.println(Arrays.toString(arr2)); var arr3 = new long[5]; // operand表明正在計算的元素索引 Arrays.parallelSetAll(arr3, operand -> operand * 5); //這行Lambda表達式的目標類型是IntToLongFunction,該對象將會根據當前索引值計算當前元素的值。 System.out.println(Arrays.toString(arr3)); } } ---------- 運行Java捕獲輸出窗 ---------- [ios, java, fkit, fkava, android] [3, -12, -300, -4800, -144000, -2592000] [0, 5, 10, 15, 20] 輸出完成 (耗時 0 秒) - 正常終止