Java8 新特性Lambda表達(dá)式實(shí)例詳解
Java8 新特性Lambda表達(dá)式實(shí)例詳解
在介紹Lambda表達(dá)式之前,我們先來看只有單個(gè)方法的Interface(通常我們稱之為回調(diào)接口):
public interface OnClickListener { void onClick(View v); }
我們是這樣使用它的:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setText("lalala"); } });
這種回調(diào)模式在各種框架中非常流行,但是像上面這樣的匿名內(nèi)部類并不是一個(gè)好的選擇,因?yàn)椋?/p>
- 語(yǔ)法冗余;
- 匿名內(nèi)部類中的this指針和變量容易產(chǎn)生誤解;
- 無法捕獲非final局部變量;
- 非靜態(tài)內(nèi)部類默認(rèn)持有外部類的引用,部分情況下會(huì)導(dǎo)致外部類無法被GC回收,導(dǎo)致內(nèi)存泄露。
令人高興的是Java8為我們帶來了Lambda,下面我們看看利用Lambda如何實(shí)現(xiàn)上面的功能:
button.setOnClickListener(v -> v.setText("lalala"));
怎么樣?!五行代碼用一行就搞定了?。?!
在這里補(bǔ)充個(gè)概念函數(shù)式接口;前面提到的OnClickListener接口只有一個(gè)方法,Java中大多數(shù)回調(diào)接口都有這個(gè)特征:比如Runnable和Comparator;我們把這些只擁有一個(gè)方法的接口稱之為函數(shù)式接口。
一、Lambda表達(dá)式
匿名內(nèi)部類最大的問題在于其冗余的語(yǔ)法,比如前面的OnClickListener中五行代碼僅有一行是在執(zhí)行任務(wù)。Lambda表達(dá)式是匿名方法,前面我們也看到了它用極其輕量的語(yǔ)法解決了這一問題。
下面給大家看幾個(gè)Lambda表達(dá)式的例子:
(int x, int y) -> x + y //接收x和y兩個(gè)整形參數(shù)并返回他們的和 () -> 66 //不接收任何參數(shù)直接返回66 (String name) -> {System.out.println(name);} //接收一個(gè)字符串然后打印出來 (View view) -> {view.setText("lalala");} //接收一個(gè)View對(duì)象并調(diào)用setText方法
Lambda表達(dá)式語(yǔ)法由參數(shù)列表、->和函數(shù)體組成。函數(shù)體既可以是一個(gè)表達(dá)式也可以是一個(gè)代碼塊。
表達(dá)式:表達(dá)式會(huì)被執(zhí)行然后返回結(jié)果。它簡(jiǎn)化掉了return關(guān)鍵字。
代碼塊:顧名思義就是一坨代碼,和普通方法中的語(yǔ)句一樣。
二、目標(biāo)類型
通過前面的例子我們可以看到,lambda表達(dá)式?jīng)]有名字,那我們?cè)趺粗浪念愋湍??答案是通過上下文推導(dǎo)而來的。例如,下面的表達(dá)式的類型是OnClickListener
OnClickListener listener = (View v) -> {v.setText("lalala");};
這就意味著同樣的lambda表達(dá)式在不同的上下文里有不同的類型
Runnable runnable = () -> doSomething(); //這個(gè)表達(dá)式是Runnable類型的 Callback callback = () -> doSomething(); //這個(gè)表達(dá)式是Callback類型的
編譯器利用lambda表達(dá)式所在的上下文所期待的類型來推導(dǎo)表達(dá)式的類型,這個(gè)被期待的類型被稱為目標(biāo)類型。lambda表達(dá)式只能出現(xiàn)在目標(biāo)類型為函數(shù)式接口的上下文中。
Lambda表達(dá)式的類型和目標(biāo)類型的方法簽名必須一致,編譯器會(huì)對(duì)此做檢查,一個(gè)lambda表達(dá)式要想賦值給目標(biāo)類型T則必須滿足下面所有的條件:
- T是一個(gè)函數(shù)式接口
- lambda表達(dá)式的參數(shù)必須和T的方法參數(shù)在數(shù)量、類型和順序上一致(一一對(duì)應(yīng))
- lambda表達(dá)式的返回值必須和T的方法的返回值一致或者是它的子類
- lambda表達(dá)式拋出的異常和T的方法的異常一致或者是它的子類
由于目標(biāo)類型是知道lambda表達(dá)式的參數(shù)類型,所以我們沒必要把已知的類型重復(fù)一遍。也就是說lambda表達(dá)式的參數(shù)類型可以從目標(biāo)類型獲取:
//編譯器可以推導(dǎo)出s1和s2是String類型 Comparator<String> c = (s1, s2) -> s1.compareTo(s2); //當(dāng)表達(dá)式的參數(shù)只有一個(gè)時(shí)括號(hào)也是可以省略的 button.setOnClickListener(v -> v.setText("lalala"));
ps: Java7中的泛型方法和<>構(gòu)造器也是通過目標(biāo)類型來進(jìn)行類型推導(dǎo)的,如:
List<Integer> intList = Collections.emptyList>(); List<String> strList = new ArrayList<>();
三、作用域
在內(nèi)部類中使用變量名和this非常容易出錯(cuò)。內(nèi)部類通過繼承得到的成員變量(包括來說object的)可能會(huì)把外部類的成員變量覆蓋掉,未做限制的this引用會(huì)指向內(nèi)部類自己而非外部類。
而lambda表達(dá)式的語(yǔ)義就十分簡(jiǎn)單:它不會(huì)從父類中繼承任何變量,也不用引入新的作用域。lambda表達(dá)式的參數(shù)及函數(shù)體里面的變量和它外部環(huán)境的變量具有相同的語(yǔ)義(this關(guān)鍵字也是一樣)。
下面我們舉個(gè)栗子吧!
public class HelloLambda { Runnable r1 = () -> System.out.println(this); Runnable r2 = () -> System.out.println(toString()); @Override public String toString() { return "Hello, lambda!"; } public static void main(String[] args) { new HelloLambda().r1.run(); new HelloLambda().r2.run(); } }
上面的代碼最終會(huì)打印兩個(gè)Hello, lambda!,與之相類似的內(nèi)部類則會(huì)打印出類似HelloLambda$1@32a890和HelloLambda$1@6b32098這種出乎意料的字符串。
總結(jié):基于詞法作用域的理念,lambda表達(dá)式不可以掩蓋任何其所在上下文的局部變量。
四、變量捕獲
在Java7中,編譯器對(duì)內(nèi)部類中引用的外部變量(即捕獲的變量)要求非常嚴(yán)格:如果捕獲的變量沒有被聲明為final就會(huì)產(chǎn)生一個(gè)編譯錯(cuò)誤。但是在Java8中放寬了這一限制–對(duì)于lambda表達(dá)式和內(nèi)部類,允許在其中捕獲那些符合有效只讀的局部變量(如果一個(gè)局部變量在初始化后從未被修改過,那么它就是有效只讀)。
Runnable getRunnable(String name){ String hello = "hello"; return () -> System.out.println(hello+","+name); }
對(duì)于this的引用以及通過this對(duì)未限定字段的引用和未限定方法的調(diào)用本質(zhì)上都屬于使用final局部變量。包含此類引用的lambda表達(dá)式相當(dāng)于捕獲了this實(shí)例。在其他情況下,lambda對(duì)象不會(huì)保留任何對(duì)this的應(yīng)用。
這個(gè)特性對(duì)內(nèi)存管理是極好的:要知道在java中一個(gè)非靜態(tài)內(nèi)部類會(huì)默認(rèn)持有外部類實(shí)例的強(qiáng)引用,這往往會(huì)造成內(nèi)存泄露。而在lambda表達(dá)式中如果沒有捕獲外部類成員則不會(huì)保留對(duì)外部類實(shí)例的引用。
不過盡管Java8放寬了對(duì)捕獲變量的語(yǔ)法限制,但試圖修改捕獲變量的行為是被禁止的,比如下面這個(gè)例子就是非法的:
int sum = 0; list.forEach(i -> {sum += i;});
為什么要禁止這種行為呢?因?yàn)檫@樣的lambda表達(dá)式很容易引起race condition
lambda表達(dá)式不支持修改捕獲變量的另外一個(gè)原因是我們可以使用更好的方式來實(shí)現(xiàn)同樣的效果:使用規(guī)約(condition)。java.util.stream包提供了各種規(guī)約操作,關(guān)于Java8中的Stream API我們放到下一章介紹。
五、方法引用
lambda表達(dá)式允許我們定義一個(gè)匿名方法,并以函數(shù)式接口的方式使用它。Java8能夠在已有的方法上實(shí)現(xiàn)同樣的特性。
方法引用和lambda表達(dá)式擁有相同的特性(他們都需要一個(gè)目標(biāo)類型,并且需要被轉(zhuǎn)化為函數(shù)式接口的實(shí)例),不過我們不需要為方法引用提供方法體,我們可以直接通過方法名引用已有方法。
以下面的代碼為例,假設(shè)我們要按照userName排序
class User{ private String userName; public String getUserName() { return userName; } ... } List<User> users = new ArrayList<>(); Comparator<User> comparator = Comparator.comparing(u -> u.getUserName()); Collections.sort(users, comparator);
我們可以用方法引用替換上面的lambda表達(dá)式
Comparator<User> comparator = Comparator.comparing(User::getUserName);
這里的User::getUserName被看做是lambda表達(dá)式的簡(jiǎn)寫形式。盡管方法引用不一定會(huì)把代碼變得更緊湊,但它擁有更明確的語(yǔ)義–如果我們想要調(diào)用的方法擁有一個(gè)名字,那么我們就可以通過方法名調(diào)用它。
方法引用有很多種,它們的語(yǔ)法如下:
- 靜態(tài)方法引用:ClassName::methodName
- 實(shí)例上的實(shí)例方法引用:instanceReference::methodName
- 超類上的實(shí)例方法引用:super::methodName
- 類型上的實(shí)例方法引用:ClassName::methodName
- 構(gòu)造方法引用:Class::new
- 數(shù)組構(gòu)造方法引用:TypeName[]::new
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Java8新特性lambda表達(dá)式有什么用(用法實(shí)例)
- Java8新特性Lambda表達(dá)式的一些復(fù)雜用法總結(jié)
- Java8新特性之Lambda表達(dá)式淺析
- Java8中l(wèi)ambda表達(dá)式的應(yīng)用及一些泛型相關(guān)知識(shí)
- Java8 Lambda表達(dá)式詳解及實(shí)例
- Java8中的 Lambda表達(dá)式教程
- Java8與Scala中的Lambda表達(dá)式深入講解
- Java8中的lambda表達(dá)式入門教程
- 30分鐘入門Java8之lambda表達(dá)式學(xué)習(xí)
- Java8中Lambda表達(dá)式的理解與應(yīng)用
相關(guān)文章
Java實(shí)現(xiàn)Redis分布式鎖的三種方案匯總
setnx、Redisson、RedLock?都可以實(shí)現(xiàn)分布式鎖,從易到難得排序?yàn)椋簊etnx?<?Redisson?<?RedLock,本文為大家整理了三種方法的實(shí)現(xiàn),希望對(duì)大家有所幫助2023-11-11SpringBoot如何切換成其它的嵌入式Servlet容器(Jetty和Undertow)
這篇文章主要介紹了SpringBoot如何切換成其它的嵌入式Servlet容器(Jetty和Undertow),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07MyBatis CodeHelperPro激活方法詳細(xì)教程
MyBatisCodeHelper-Pro是IDEA下的一個(gè)插件,功能類似mybatis plugin,今天小編給大家分享MyBatis CodeHelperPro激活方法,需要的朋友跟隨小編一起看看吧2021-07-07解讀CommandLineRunner和@PostConstruct區(qū)別與應(yīng)用場(chǎng)景
這篇文章主要介紹了解讀CommandLineRunner和@PostConstruct區(qū)別與應(yīng)用場(chǎng)景,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12基于Java實(shí)現(xiàn)無向環(huán)和有向環(huán)的檢測(cè)
這篇文章主要介紹了如何在?Java?中實(shí)現(xiàn)無向環(huán)和有向環(huán)的檢測(cè),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2022-04-04IntelliJ?IDEA快速查詢maven依賴關(guān)系圖文教程
Maven提供了來查看依賴關(guān)系,而IDE往往提供了更加便利的方式,比如Eclipse或者IDEA都有類似的功能,下面這篇文章主要給大家介紹了關(guān)于IntelliJ?IDEA快速查詢maven依賴關(guān)系的相關(guān)資料,需要的朋友可以參考下2023-11-11python和java哪個(gè)學(xué)起來更簡(jiǎn)單
在本篇內(nèi)容里小編給大家分享的是一篇關(guān)于python和java哪個(gè)學(xué)起來更簡(jiǎn)單的相關(guān)內(nèi)容,有興趣的朋友們參考下。2020-06-06SpringBoot2.6.x升級(jí)后循環(huán)依賴及Swagger無法使用問題
這篇文章主要為大家介紹了SpringBoot2.6.x升級(jí)后循環(huán)依賴及Swagger無法使用問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06解析Java中的定時(shí)器及使用定時(shí)器制作彈彈球游戲的示例
這篇文章主要介紹了Java中的定時(shí)器及使用定時(shí)器制作彈彈球游戲的示例,文中同時(shí)也分析了定時(shí)器timer的缺點(diǎn)及相關(guān)替代方案,需要的朋友可以參考下2016-02-02