Java8中Lambda表達(dá)式的理解與應(yīng)用
簡(jiǎn)介
Lambda表達(dá)式是一個(gè)可傳遞的代碼塊,可以在以后執(zhí)行一次或多次;
下面貼個(gè)對(duì)比代碼:
// Java8之前:舊的寫法 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("old run"); } }; Thread t = new Thread(runnable); // Java8之后:新的寫法 Runnable runnable1 = ()->{ System.out.println("lambda run"); }; Thread t1 = new Thread(runnable1);
可以看到,有了lambda,代碼變得簡(jiǎn)潔多了
你可以把lambda當(dāng)作一個(gè)語(yǔ)法糖
下面讓我們一起來(lái)探索lambda的美好世界吧
正文
1. lambda的語(yǔ)法
下面分別說(shuō)下語(yǔ)法中的三個(gè)組成部分
參數(shù): ( Dog dog )
參數(shù)類型可省略(當(dāng)編譯器可以自動(dòng)推導(dǎo)時(shí)),比如
Comparator<String> comparatorTest = (a, b)->a.length()-b.length();
,可以推導(dǎo)出a,b都為String當(dāng)參數(shù)類型可省略,且只有一個(gè)參數(shù)時(shí),括弧也可以省略(但是個(gè)人習(xí)慣保留)
符號(hào):->
主體:
{ System.out.println("javalover"); }
如果是一條語(yǔ)句,則需要加大括號(hào)和分號(hào)
{;}
(比如上圖所示)如果是一個(gè)表達(dá)式,則直接寫,啥也不加(比如
a.length()- b.length()
)
2. 為啥引入lambda
為了簡(jiǎn)化代碼
因?yàn)镴ava是面向?qū)ο笳Z(yǔ)言,所以在lambda出現(xiàn)之前,我們需要先構(gòu)造一個(gè)對(duì)象,然后在對(duì)象的方法中實(shí)現(xiàn)具體的內(nèi)容,再把構(gòu)造的對(duì)象傳遞給某個(gè)對(duì)象或方法
但是有了lambda以后,我們可以直接將代碼塊傳遞給對(duì)象或方法
現(xiàn)在再回頭看下開頭的例子
可以看到,用了lambda表達(dá)式后,少了很多模板代碼,只剩下一個(gè)代碼塊(最核心的部分)
3. 什么是函數(shù)式接口
就是只定義了一個(gè)抽象方法的接口
正例:有多個(gè)默認(rèn)方法,但是如果只有一個(gè)抽象方法,那它就是函數(shù)式接口,示例代碼如下
@FunctionalInterface public?interface?FunctionInterfaceDemo?{ ? ?void?abstractFun(); ? ?default?void?fun1(){ ? ? ? ?System.out.println("fun1"); ? ? ? } ? ?default?void?fun2(){ ? ? ? ?System.out.println("fun2"); ? } ? }
這里的注解@FunctionalInterface可以省略,但是建議加上,就是為了告訴編譯器,這是一個(gè)函數(shù)式接口,此時(shí)如果該接口有多個(gè)抽象方法,那么編譯器就會(huì)報(bào)錯(cuò)
反例:比如A extends B,A和B各有一個(gè)抽象方法,那么A就不是函數(shù)式接口,示例代碼如下
// 編譯器會(huì)報(bào)錯(cuò),Multiple non-overriding abstract methods found in XXX @FunctionalInterface public?interface?NoFunctionInterfaceDemo?extends?FunctionInterfaceDemo{ ?void?abstractFun2(); }
上面的父接口FunctionInterfaceDemo中已經(jīng)有了一個(gè)抽象方法,此時(shí)NoFunctionInterfaceDemo又定義了一個(gè)抽象方法,結(jié)果編譯器就提示了:存在多個(gè)抽象方法
在Java8之前,其實(shí)我們已經(jīng)接觸過(guò)函數(shù)式接口
比如Runnable 和 Comparable
只是沒有注解@FunctionalInterface。
那這個(gè)函數(shù)式接口要怎么用呢?
配合lambda食用,效果最佳(就是把lambda傳遞給函數(shù)式接口),示例代碼如下:
new?Thread(()?->?System.out.println("run")).start();
其中用到的函數(shù)式接口是Runnable
4. 什么是行為參數(shù)化
就是把行為定義成參數(shù),行為就是函數(shù)式接口
類似泛型中的類型參數(shù)化,類型參數(shù)化是把類型定義成參數(shù)
行為參數(shù)化,通俗點(diǎn)來(lái)說(shuō):
就是用函數(shù)式接口做形參
然后傳入接口的各種實(shí)現(xiàn)內(nèi)容(即lambda表達(dá)式)作為實(shí)參
最后在lambda內(nèi)實(shí)現(xiàn)各種行為(好像又回到多態(tài)的那一節(jié)了?這也是為啥多態(tài)是Java的三大特性的原因之一,應(yīng)用太廣泛了)
這樣來(lái)看的話,行為參數(shù)化和設(shè)計(jì)模式中的策略模式有點(diǎn)像了(后面章節(jié)會(huì)分別講常用的幾種設(shè)計(jì)模式)
下面我們手寫一個(gè)函數(shù)式接口來(lái)加深理解吧
5. 手寫一個(gè)函數(shù)式接口
下面我們循序漸進(jìn),先從簡(jiǎn)單的需求開始
第一步:比如我們想要讀取某個(gè)文件,那可以有如下方法:
public?static?String?processFile()?throws?IOException?{ ? ?// Java7新增的語(yǔ)法,try(){},可自動(dòng)關(guān)閉資源,減少了代碼的臃腫 ? ?try(?BufferedReader?bufferedReader?= ? ? ? ?new?BufferedReader(new??FileReader("./test.txt"))){ ? ? ? ?return?bufferedReader.readLine(); ? } }
可以看到,核心的行為動(dòng)作就是 return bufferedReader.readLine();
,表示讀取第一行的數(shù)據(jù)并返回
那如果我們想要讀取兩行呢?三行?
第二步:這時(shí)就需要用到上面的函數(shù)式接口了,下面就是我們自己編寫的函數(shù)式接口
@FunctionalInterface interface?FileReadInterface{ // 這里接受一個(gè)BufferedReader對(duì)象,返回一個(gè)String對(duì)象 ? ?String?process(BufferedReader?reader)?throws?IOException; }
可以看到,只有一個(gè)抽象方法process(),它就是用來(lái)處理第一步中的核心動(dòng)作(讀取文件內(nèi)容)
至于想讀取多少內(nèi)容,那就需要我們?cè)趌ambda表達(dá)式中定義了
第三步:接下來(lái)我們定義多個(gè)lambda表達(dá)式,用來(lái)傳遞給函數(shù)式接口,其中每個(gè)lambda表達(dá)式就代表了一種不同的行為,代碼如下:
// 讀取一行 FileReadInterface?fileReadInterface?=?reader?->?reader.readLine(); // 讀取兩行 FileReadInterface?fileReadInterface2?=?reader?->?reader.readLine()?+?reader.readLine();
第四步:我們需要修改第一步的
processFile()
,讓其接受一個(gè)函數(shù)式接口,并調(diào)用其中的抽象方法,代碼如下:
// 參數(shù)為第二步我們自己手寫的函數(shù)式接口 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 這里我們不再自己定義行為,而是交給函數(shù)式接口的抽象方法來(lái)處理,然后通過(guò)lambda表達(dá)式的傳入來(lái)實(shí)現(xiàn)多個(gè)行為 return fileReadInterface.process(bufferedReader); } }
第五步:拼接后,完整代碼如下:
public class FileReaderDemo { public static void main(String[] args) throws IOException { // 第三步: // lambda表達(dá)式1 傳給 函數(shù)式接口:只讀取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // lambda表達(dá)式2 傳給 函數(shù)式接口:只讀取兩行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine(); // 最后一步: 不同的函數(shù)式接口的實(shí)現(xiàn),表現(xiàn)出不同的行為 String str1 = processFile(fileReadInterface); String str2 = processFile(fileReadInterface2); System.out.println(str1); System.out.println(str2); } // 第四步: 讀取文件方法,接受函數(shù)式接口作為參數(shù) public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 調(diào)用函數(shù)式接口中的抽象方法來(lái)處理數(shù)據(jù) return fileReadInterface.process(bufferedReader); } } // 第一步: public static String processFile() throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ return bufferReader.readLine(); } } } // 第二步: 我們手寫的函數(shù)式接口 @FunctionalInterface interface FileReadInterface{ String process(BufferedReader reader) throws IOException; }
其實(shí)你會(huì)發(fā)現(xiàn),我們手寫的這個(gè)函數(shù)式接口,其實(shí)就是Function<T>
去除泛型化后的接口,如下所示:
@FunctionalInterface public?interface?Function<T,?R>?{ // 都是接受一個(gè)參數(shù),返回另一個(gè)參數(shù) ?R?apply(T?t); }
下面我們列出Java中常用的一些函數(shù)式接口,你會(huì)發(fā)現(xiàn)自帶的已經(jīng)夠用了,基本不會(huì)需要我們自己去寫
這里的手寫只是為了自己實(shí)現(xiàn)一遍,可以加深理解程度
6. 常用的函數(shù)式接口
7. 什么是方法引用
我們先看一個(gè)例子
前面我們寫的lambda表達(dá)式,其實(shí)還可以簡(jiǎn)化,比如
// 簡(jiǎn)化前 Function<Cat,?Integer>?function?=?c->c.getAge(); // 簡(jiǎn)化后 Function<Cat,?Integer>?function2?=?Cat::getAge;
其中簡(jiǎn)化后的Cat::getAge
,我們就叫做方法引用
方法引用就是引用類或?qū)ο蟮姆椒?/strong>;
下面我們列出方法引用的三種情況:
Object::instanceMethod(對(duì)象的實(shí)例方法)
Class::staticMethod(類的靜態(tài)方法)
Class::instanceMethod(類的實(shí)例方法)
像我們上面舉的例子就是第三種:類的實(shí)例方法
下面我們用代碼演示上面的三種方法:
public class ReferenceDemo { public static void main(String[] args) { // 第一種:引用對(duì)象的實(shí)例方法 Cat cat = new Cat(1); Function<Cat, Integer> methodRef1 = cat::getSum; // 第二種:引用類的靜態(tài)方法 Supplier<Integer> methodRef2 = Cat::getAverageAge; // 第三種:引用類的實(shí)例方法 Function<Cat, Integer> methodRef3 = Cat::getAge; } } class Cat { int age; public Cat(int age) { this.age = age; } // 獲取貓的平均年齡 public static int getAverageAge(){ return 15; } // 獲取兩只貓的年齡總和 public int getSum(Cat cat){ return cat.getAge() + this.getAge(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
為啥要用這個(gè)方法引用呢?
方法引用好比lambda表達(dá)式的語(yǔ)法糖,語(yǔ)法更加簡(jiǎn)潔,清晰
一看就知道是調(diào)用哪個(gè)類或?qū)ο蟮哪膫€(gè)方法
8. 什么是構(gòu)造引用
上面介紹了方法引用,就是直接引用某個(gè)方法
這里的構(gòu)造引用同理可得,就是引用某個(gè)類的構(gòu)造方法
構(gòu)造引用的表達(dá)式為:Class::new
,僅此一種
如果你有多個(gè)構(gòu)造函數(shù),那編譯器會(huì)自己進(jìn)行推斷參數(shù)(你看看,多好,多簡(jiǎn)潔)
比如下面的代碼:
// 這里調(diào)用 new Cat() Supplier<Cat>?constructRef1?=?Cat::new; // 這里調(diào)用 new Cat(Integer) Function<Integer,?Cat>?constructRef2?=?Cat::new;
9. lambda表達(dá)式中引入外部變量的限制
要求引入lambda表達(dá)式中的變量,必須是最終變量,即該變量不會(huì)再被修改
比如下面的代碼:
public static void main(String[] args) { String str = "javalover.cc"; Runnable runnable = ()->{ str = "1";// 這里會(huì)報(bào)錯(cuò),因?yàn)樾薷牧藄tr引用的指向 System.out.println(str); } }
可以看到,lambda表達(dá)式引用了外面的str引用,但是又在表達(dá)式內(nèi)部做了修改,結(jié)果就報(bào)錯(cuò)了
為啥要有這個(gè)限制呢?
為了線程安全,因?yàn)閘ambda表達(dá)式有一個(gè)好處就是只在需要的時(shí)候才會(huì)執(zhí)行,而不是調(diào)用后立馬執(zhí)行
這樣就會(huì)存在多個(gè)線程同時(shí)執(zhí)行的并發(fā)問題
所以Java就從根源上解決:不讓變量被修改,都是只讀的
那你可能好奇,我不把str的修改代碼放到表達(dá)式內(nèi)部可以嗎?
也不行,道理是一樣的,只要lambda有用到這個(gè)變量,那這個(gè)變量不管是在哪里被修改,都是不允許的
不然的話,我這邊先執(zhí)行了一次lambda表達(dá)式,結(jié)果你就改了變量值,那我第二次執(zhí)行l(wèi)ambda,不就亂了嗎
10. lambda的組合操作
最后是lambda的必殺技:組合操作
在這里叫組合或者復(fù)合都可以
概述:組合操作就是先用一個(gè)lambda表達(dá)式,然后再在后面組合另一個(gè)lambda表達(dá)式,然后再在后面組合另另一個(gè)lambda表達(dá)式,然后。。。有點(diǎn)像是鏈?zhǔn)讲僮?/p>
學(xué)過(guò)JS的都知道Promise,里面的鏈?zhǔn)讲僮骶秃瓦@里的組合操作很像
用過(guò)Lombok的朋友,應(yīng)該很熟悉@Builder注解,其實(shí)就是構(gòu)造者模式
下面我們用代碼演示下組合操作:
// 重點(diǎn)代碼 public class ComposeDemo { public static void main(String[] args) { List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1)); // 1. 先按年齡排序(默認(rèn)遞增) // Dog::getAge, 上面介紹的方法引用 // comparingInt, 是Comparator的一個(gè)靜態(tài)方法,返回Comparator<T> Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge); // 2. 如果有相同的年齡,則年齡相同的再按體重排序(如果年齡已經(jīng)比較出大小,則下面的體重就不會(huì)再去比較) Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);; // 3. 調(diào)用list對(duì)象的sort方法排序,參數(shù)是Comparator<? super Dog> list.sort(comparableAge.thenComparing(comparableWeight)); System.out.println(list); } } // 非重點(diǎn)代碼 class Dog{ private int age; private int weight; public Dog(int age, int weight) { this.age = age; this.weight = weight; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Dog{" + "age=" + age + ", weight=" + weight + '}'; } }
輸出:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]
比較的流程如下所示:
總結(jié)
lambda的語(yǔ)法: 參數(shù)+符合+表達(dá)式或語(yǔ)句,比如
(a,b)->{System.out.println("javalover.cc");}
函數(shù)式接口:只有一個(gè)抽象方法,最好加@FunctionalInterface,這樣編譯器可及時(shí)發(fā)現(xiàn)錯(cuò)誤,javadoc也說(shuō)明這是一個(gè)函數(shù)式接口(可讀性)
行為參數(shù)化:就是函數(shù)式接口作為參數(shù),然后再將lambda表達(dá)式傳給函數(shù)式接口,通過(guò)不同的lambda內(nèi)容實(shí)現(xiàn)不同的行為
方法引用:lambda的語(yǔ)法糖,總共有三種:
Class::instanceMethod(類的實(shí)例方法)
Object::instanceMethod(對(duì)象的實(shí)例方法)
Class::staticMethod(類的靜態(tài)方法)
構(gòu)造引用:就一種,編譯器自己可判斷是哪個(gè)構(gòu)造函數(shù),語(yǔ)法為
Class::new
在lambda中引入外部變量,必須保證這個(gè)變量是最終變量,即不再被修改
lambda的組合操作,就是鏈?zhǔn)讲僮鳎M合是通過(guò)函數(shù)式接口的靜態(tài)方法來(lái)組合(靜態(tài)方法會(huì)返回另一個(gè)函數(shù)式接口的對(duì)象)
比如list.sort(comparableAge.thenComparing(comparableWeight));
到此這篇關(guān)于Java8中Lambda表達(dá)式的理解與應(yīng)用的文章就介紹到這了,更多相關(guān)Java8中Lambda表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis報(bào)錯(cuò)?resultMapException的解決
這篇文章主要介紹了mybatis報(bào)錯(cuò)?resultMapException的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01通過(guò)Java測(cè)試幾種壓縮算法的性能(附測(cè)試代碼下載)
這篇文章主要介紹了通過(guò)Java測(cè)試幾種壓縮算法的實(shí)際性能的一個(gè)實(shí)驗(yàn),包括Java自帶的deflate與GZIP壓縮方式,還是有一定借鑒意義的,需要的朋友可以參考下2015-12-12詳解Spring Cloud Consul 實(shí)現(xiàn)服務(wù)注冊(cè)和發(fā)現(xiàn)
這篇文章主要介紹了Spring Cloud Consul 實(shí)現(xiàn)服務(wù)注冊(cè)和發(fā)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03SpringBoot集成Mybatis+xml格式的sql配置文件操作
這篇文章主要介紹了SpringBoot集成Mybatis+xml格式的sql配置文件操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07用Java實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07Spring注解@Qualifier的詳細(xì)用法你知道幾種
本文給大家分享Spring注解@Qualifier的詳細(xì)用法,包括@Autowired和@Resource區(qū)別介紹,本文通過(guò)示例代碼給大家詳細(xì)介紹,感興趣的朋友跟隨小編一起看看吧2021-07-07解決mapper.xml中resultType映射類型的問題
這篇文章主要介紹了解決mapper.xml中resultType映射類型的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的圖文教程
這篇文章主要介紹了IDEA使用Mybatis插件 MyBatisCodeHelper-Pro的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09