Java8 出現(xiàn)的時間已經(jīng)不算短了,免費維護期馬上也要到期了可训,官方已經(jīng)開始推薦使用 Java11昌妹。
Java8 是革命性的一個版本,一直以來握截,Java 最受詬病的地方就是代碼寫起來很啰嗦飞崖,僅僅寫一個 HelloWorld 都需要些很多的樣板代碼。
在 Java8 推出之后谨胞,啰嗦的代碼有了很大的改觀固歪,Java 也可以寫出簡單優(yōu)美的代碼。最明顯的改觀就是 Java 開始支持函數(shù)式編程胯努。
函數(shù)式編程的定義很晦澀牢裳,但是我們可以將函數(shù)式編程理解為函數(shù)本身可以作為參數(shù)進行傳遞,就是說叶沛,參數(shù)不僅僅可以是數(shù)據(jù)蒲讯,也可以是行為(函數(shù)或者方法的實現(xiàn)其實就是邏輯行為)。
可能是 Java8 步子跨的太大灰署,以至于現(xiàn)在還有很多人沒有趕上來判帮,依然用 Java8 在寫 Java5 風格的代碼局嘁。
這篇文章的目的就是徹底說清楚 Java8 的變化,以及快速全面的使用 Java8 的特性晦墙,讓 Java 代碼優(yōu)雅起來悦昵。
Java面試突擊、阿里巴巴Java面試筆記晌畅、面試答案關注我私信回復【444】獲得免費獲取方式但指!
函數(shù)式接口
在開始說 Java8 的函數(shù)式編程之前,我們需要說明一下抗楔,在 Java8 中新增加的一個概念棋凳,叫函數(shù)式接口。
這個函數(shù)式接口是 Java8 實現(xiàn)函數(shù)式編程的基礎连躏,正是這類接口的存在贫橙,才能把函數(shù)(方法)當做參數(shù)進行傳遞,至少表面上看起來是這樣的反粥,但是實際上傳遞的還是對象,這個問題我們下面再討論疲迂,先回到函數(shù)式接口才顿。
下面就是一個函數(shù)式接口:
publicinterfaceAction{publicvoidaction();}
這個函數(shù)式看起來和普通的接口沒有什么區(qū)別,唯一的區(qū)別是函數(shù)式接口只能有一個抽象方法尤蒿。
如果你想讓別人立馬理解這個接口是函數(shù)式接口郑气,可以加上?@FunctionalInterface?注解,這個注解不會提供任何額外的功能腰池,僅僅用來表示這個接口是一個函數(shù)式接口尾组。
@FunctionalInterfacepublicinterfaceAction{publicvoidaction();}
只能有一個抽象方法是為了更方便的把函數(shù)作為參數(shù)來傳遞,這個后面再細說示弓。
我們可以根據(jù)自己的需要來定義函數(shù)式接口讳侨,JDK 為了使用方便,內(nèi)置了很多函數(shù)式接口奏属,日常使用完全夠了跨跨。
常用的函數(shù)接口有:
1.Function
2.Predicate
3.Consumer
函數(shù)式接口其實就這么簡單,看到這里你可能還是對函數(shù)式接口不是很理解囱皿,沒關系勇婴,現(xiàn)在你僅僅只需要記住函數(shù)式接口就是模板。
Lambda 表達式
說起 Java8 的函數(shù)式編程嘱腥,很多人都知道 lambda 表達式耕渴,這也是 Java8 中最容易被人記住的地方。
先來通過一個直觀的例子來了解一下 lambda齿兔,在操作 ArrayList 等數(shù)據(jù)結(jié)構(gòu)時橱脸,我們有可能要對其中的數(shù)據(jù)進行排序础米,比如:
Comparator comparator =newComparator() {publicintcompare(Integer i1, Integer i2){returni1.comparaTo(i2);? ? }}
在上面的代碼中,真正有用的代碼也有比較大小的那行慰技,其他的都是樣板代碼椭盏。在這樣的情況下,lambda 就很有用吻商。
Comparator comparator =(Integer i1, Integer i2)->{returni1.compareTo(i2);}
這樣看起來是不是很簡單了掏颊,但是還是繼續(xù)優(yōu)化,可以把返回參數(shù)的部分也省略:
Comparator comparator =(Integer i1, Integer i2)->i1.compareTo(i2)
既然兩個參數(shù)都是 Integer 那是不是也可以省略艾帐,最后就得到了下面這樣的形式:
Comparator comparator =(i1, i2)->i1.compareTo(i2)
這就是 lambda 的力量乌叶,可以把上面那么多的代碼濃縮成一行。
lambda 其實就是一段代碼柒爸,但也不僅僅是一段代碼准浴,再簡單的 lambda 也會有三部分,參數(shù)列表捎稚,箭頭和 lambda 主體乐横,上面的?(i1, i2)?就是參數(shù)列表,?i1.compareTo(i2)?就是 lambda 主體今野,箭頭把這兩部分隔開了葡公。
lambda 是匿名的,這點和 Java 中的匿名實現(xiàn)類有點像(本質(zhì)上一樣)条霜,而且它是一種函數(shù)催什,不屬于任何類(屬于類的函數(shù)稱之為方法),并且可以作為參數(shù)進行傳遞宰睡,而且還很簡潔蒲凶。
看到這里,可能就有人猜到函數(shù)式接口和 lambda 之間可能有某些關系了拆内,沒錯旋圆,我們已經(jīng)快說到最重要的部分。
在這之前再來理解一個概念麸恍,函數(shù)簽名臂聋,函數(shù)簽名為可以表示一類函數(shù),如果兩個函數(shù)的以下部分相同或南,就可以說這兩個函數(shù)的簽名一致:
函數(shù)參數(shù)及其類型
返回值及其類型
可能會拋出的異常
還有訪問控制符(public 等等)
最關鍵的地方來了孩等,只要 lambda 和函數(shù)式接口方法的簽名一致,lambda 表達式就可以作為參數(shù)傳入到以該函數(shù)式接口為參數(shù)類型的方法中采够。
來看個詳細的例子肄方,?Comparator?接口的定義如下:
@FunctionalInterfacepublicinterfaceComparator{intcompare(T o1, T o2);}
雖然 Comparator 中方法不止一個,但是抽象方法只有?compare?一個蹬癌,上面的 lambda 完全可以作為 compare 方法的實現(xiàn)权她,實際上虹茶,lambda 表達式確實是作為函數(shù)式接口抽象方法的實現(xiàn),而且隅要,lambda 表達式為作為整個函數(shù)接口的實例蝴罪。
到這里,真相大白步清,Java 8 的雖然支持了函數(shù)式編程要门,這不代表函數(shù)就是 Java 中的一等公民了,每一個函數(shù)其實還是被包裹成一個對象廓啊,對象依然是 Java 中的一等公民欢搜。
所以簡單來說,只要 lambda 的表達式的參數(shù)和返回類型可以與函數(shù)式接口中的抽象方法對的上谴轮,lambda 就可以作為該函數(shù)式接口的實現(xiàn)進行傳遞炒瘟。
比如上面列舉的幾種函數(shù)式接口,其實就是對一些通用函數(shù)的抽象第步,比如 Function 函數(shù)式接口如下:
@FunctionalInterfacepublicinterfaceFunction{Rapply(T t);}
這種就代表接受一個參數(shù)疮装,返回另一個值的函數(shù),只要滿足這個要求的 lambda 表達式都可以作為 它的實現(xiàn)粘都。
再比如 Predicate 接口斩个,代表接受一個參數(shù)返回一個布爾值的函數(shù):
@FunctionalInterfacepublicinterfacePredicate{booleantest(T t);}
Comsumer 接口表示接受一個參數(shù),什么都不返回的函數(shù):
@FunctionalInterfacepublicinterfaceConsumer{voidaccept(T t);}
如果這樣理解起來還是有點困難驯杜,那就把這些函數(shù)式接口理解為 lambda 表達式的類型。
類型檢查和類型推斷
在上面我們說到了只要函數(shù)式接口抽象方法的函數(shù)簽名與 lambda 一致做个,那么就可以把 lambda 表達式作為該函數(shù)式接口的實現(xiàn)鸽心。
上面的例子中, lambda 的參數(shù)類型也是可以省略的居暖,那么 Java 是如何判斷 lambda 是否與函數(shù)式接口匹配呢顽频?
如果 lambda 表達式中,參數(shù)和返回值的類型都省略之后太闺,需要從使用 lambda 的上下文推斷出來糯景。
方法引用
本來到這里應該就很完美了,lambda 夠簡潔省骂,用它寫代碼蟀淮,可以省略很多無用的樣本代碼,但是 lambda 也不完美钞澳,因為 lambda 表達式的代碼很難復用怠惶,而且很多的 lambda 表達式僅僅就是調(diào)用了其他的方法。
這個時候轧粟,方法引用就可以派上用場了策治,比如上面的例子脓魏,其實僅僅就是調(diào)用了?Integer.compareTo()?方法:
Comparator comparator =(i1, i2)->i1.compareTo(i2)
還可以簡化成下面的樣子:
Comparatorcomparator = Integer::compareTo
使用方法引用的時候,要使用?::?通惫,而且任何方法都可以這樣被引用茂翔,無論是靜態(tài)方法還是實例方法。
方法引用可以被認為是 lambda 的語法糖履腋,使用方法引用可以讓代碼更加簡潔珊燎,更直觀,看到方法引用的名稱就能大概知道代碼的邏輯府树,并且還可以對一些代碼進行復用俐末。
寫出 Java8 風格的代碼
在 Java8 之后,很多代碼的寫法應該摒棄奄侠,下面列舉一些常見的例子卓箫。
遍歷 List
Java8 以前:
for(Integeri : list) {System.out.println(i);}
Java8 及以后:
list.forEach(System.out::println);
forEach 接收 Consumer 類型的函數(shù),而 System.out.println() 剛好就符合要求垄潮。
遍歷 Map
Java8 以前:
for(Map.Entryentry : map.entrySet()) {System.out.println("Key: "+ entry.getKey() +", Value:"+ entry.getValue()); }
Java8 及以后:
map.forEach((k, v)->{System.out.println("Key: "+ k +", Value: "+ v)});
這里的 forEach 不是接收 Consumer 類型的函數(shù)烹卒,而是接收 BiConsumer 類的函數(shù),可以用來處理 map 這種包含 key-value 類型的數(shù)據(jù)弯洗,在大多數(shù)場景下旅急,內(nèi)置的函數(shù)式接口以及足夠我們使用,只有在一些特殊的場景下才需要自己定制牡整。
這里也沒有現(xiàn)成的方法引用藐吮,所以就可以使用 lambda 表達式來實現(xiàn)。
執(zhí)行異步任務
假如要使用線程池來執(zhí)行任務逃贝。
Java8 以前:
ThreadPoolExecutor executor =newThreadPoolExecutor(10,10,0, TimeUnit.SECONDS,newArrayBlockingQueue(2000));executor.submit(newRunnable() {publicvoidrun(){? ? ? ? System.out.println("Thread pool execute");? ? }});
Java8 以后:
ThreadPoolExecutor executor =newThreadPoolExecutor(10,10,0, TimeUnit.SECONDS,newArrayBlockingQueue(2000));executor.submit(()->{? ? System.out.println("Thread pool execute");});
也可以使用 lambda 來解決這個問題谣辞,Runnable 也是一個函數(shù)式接口。
Java面試突擊沐扳、阿里巴巴Java面試筆記泥从、面試答案關注我私信回復【444】獲得免費獲取方式!
作者【Rayjun】