教育行業(yè)A股IPO第一股(股票代碼 003032)

全國(guó)咨詢/投訴熱線:400-618-4000

JDK1.8有哪些新特性?JDK1.8詳細(xì)介紹

更新時(shí)間:2019年12月20日17時(shí)05分 來(lái)源:傳智播客 瀏覽次數(shù):

JDK1.8目前在企業(yè)中已經(jīng)廣泛被應(yīng)用,今天我們將學(xué)習(xí)以下方面的新特性:

· Lambda表達(dá)式

· 函數(shù)式接口

· 方法引用

· 接口的默認(rèn)方法和靜態(tài)方法

· Optional

· Streams

· 并行數(shù)組

Lambda表達(dá)式

Lambda 表達(dá)式,也可稱為閉包,它是推動(dòng) Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。可以使代碼變的更加簡(jiǎn)潔緊湊。

⒈基本語(yǔ)法

(參數(shù)列表) -> {代碼塊}

需要注意:

· 參數(shù)類型可省略,編譯器可以自己推斷

· 如果只有一個(gè)參數(shù),圓括號(hào)可以省略

· 代碼塊如果只是一行代碼,大括號(hào)也可以省略

· 如果代碼塊是一行,且是有結(jié)果的表達(dá)式, return 可以省略

注意:事實(shí)上,把Lambda表達(dá)式可以看做是匿名內(nèi)部類的一種簡(jiǎn)寫方式。當(dāng)然,前提是這個(gè)匿名內(nèi)部類對(duì)應(yīng)的必須

是接口,而且接口中必須只有一個(gè)函數(shù)!Lambda表達(dá)式就是直接編寫函數(shù)的:參數(shù)列表、代碼體、返回值等信息,

用函數(shù)來(lái)代替完整的匿名內(nèi)部類 !

⒉用法示例

示例1:多個(gè)參數(shù)

準(zhǔn)備一個(gè)集合:

假設(shè)我們要對(duì)集合排序,我們先看JDK7的寫法,需要通過(guò)匿名內(nèi)部類來(lái)構(gòu)造一個(gè) Comparator :

如果是jdk8,我們可以使用新增的集合API:sort(Comparator c) 方法,接收一個(gè)比較器,我們用Lambda來(lái)代替Comparator 的匿名內(nèi)部類:

對(duì)比一下 Comparator 中的 compare() 方法,你會(huì)發(fā)現(xiàn):這里編寫的Lambda表達(dá)式,恰恰就是 compare() 方法的簡(jiǎn)寫形式,JDK8會(huì)把它編譯為匿名內(nèi)部類。是不是簡(jiǎn)單多了!

別著急,我們發(fā)現(xiàn)這里的代碼塊只有一行代碼,符合前面的省略規(guī)則,我們可以簡(jiǎn)寫為:

示例2:?jiǎn)蝹€(gè)參數(shù)

還以剛才的集合為例,現(xiàn)在我們想要遍歷集合中的元素,并且打印。

先用jdk1.7的方式:

jdk1.8給集合添加了一個(gè)方法:foreach() ,接收一個(gè)對(duì)元素進(jìn)行操作的函數(shù):

實(shí)例3:把Lambda賦值給變量

Lambda表達(dá)式的實(shí)質(zhì)其實(shí)還是匿名內(nèi)部類,所以我們其實(shí)可以把Lambda表達(dá)式賦值給某個(gè)變量。

不過(guò)上面的用法很少見(jiàn),一般都是直接把Lambda作為參數(shù)。

示例4:隱式final

Lambda表達(dá)式的實(shí)質(zhì)其實(shí)還是匿名內(nèi)部類,而匿名內(nèi)部類在訪問(wèn)外部局部變量時(shí),要求變量必須聲明為 final !不過(guò)我們?cè)谑褂肔ambda表達(dá)式時(shí)無(wú)需聲明 final ,這并不是說(shuō)違反了匿名內(nèi)部類的規(guī)則,因?yàn)長(zhǎng)ambda底層會(huì)隱式的把變量設(shè)置為 final ,在后續(xù)的操作中,一定不能修改該變量:

正確示范:

錯(cuò)誤案例:

函數(shù)式接口

經(jīng)過(guò)前面的學(xué)習(xí),相信大家對(duì)于Lambda表達(dá)式已經(jīng)有了初步的了解??偨Y(jié)一下:

· Lambda表達(dá)式是接口的匿名內(nèi)部類的簡(jiǎn)寫形式

· 接口必須滿足:內(nèi)部只有一個(gè)函數(shù)

其實(shí)這樣的接口,我們稱為函數(shù)式接口,我們學(xué)過(guò)的 Runnable 、Comparator 都是函數(shù)式接口的典型代表。但是在實(shí)踐中,函數(shù)接口是非常脆弱的,只要有人在接口里添加多一個(gè)方法,那么這個(gè)接口就不是函數(shù)接口了,就會(huì)導(dǎo)致編譯失敗。Java 8提供了一個(gè)特殊的注解@FunctionalInterface 來(lái)克服上面提到的脆弱性并且顯示地表明函數(shù)接口。而且jdk8版本中,對(duì)很多已經(jīng)存在的接口都添加了@FunctionalInterface 注解,例如 Runnable 接口:

另外,Jdk8默認(rèn)提供了一些函數(shù)式接口供我們使用:

⒈Function類型接口

Function代表的是有參數(shù),有返回值的函數(shù)。還有很多類似的Function接口:

看出規(guī)律了嗎?這些都是一類函數(shù)接口,在Function基礎(chǔ)上衍生出的,要么明確了參數(shù)不確定返回結(jié)果,要么明確結(jié)果不知道參數(shù)類型,要么兩者都知道。

⒉Consumer系列

Consumer系列與Function系列一樣,有各種衍生接口,這里不一一列出了。不過(guò)都具備類似的特征:那就是不返回任何結(jié)果。

⒊ Predicate系列

Supplier系列,英文翻譯就是“供應(yīng)者”,顧名思義:只產(chǎn)出,不收取。所以不接受任何參數(shù),返回T類型結(jié)果。

方法引用

方法引用使得開發(fā)者可以將已經(jīng)存在的方法作為變量來(lái)傳遞使用。方法引用可以和Lambda表達(dá)式配合使用。

⒈語(yǔ)法

總共有四類方法引用:

⒉示例

首先我們編寫一個(gè)集合工具類,提供一個(gè)方法:

可以看到這個(gè)方法接收兩個(gè)參數(shù):

· List list :需要進(jìn)行轉(zhuǎn)換的集合

· Function :函數(shù)接口,接收T類型,返回R類型。用這個(gè)函數(shù)接口對(duì)list中的元素T進(jìn)行轉(zhuǎn)換,變?yōu)镽類型

接下來(lái),我們看具體案例:

① 類的靜態(tài)方法引用

我們需要把這個(gè)集合中的元素轉(zhuǎn)為十六進(jìn)制保存,需要調(diào)用Integer.toHexString() 方法:

這個(gè)方法接收一個(gè) i 類型,返回一個(gè) String 類型,可以用來(lái)構(gòu)造一個(gè) Function 的函數(shù)接口:

我們先按照Lambda原始寫法,傳入的Lambda表達(dá)式會(huì)被編譯為 Function 接口,接口中通過(guò)Integer.toHexString(i) 對(duì)原來(lái)集合的元素進(jìn)行轉(zhuǎn)換:

上面的Lambda表達(dá)式代碼塊中,只有對(duì) Integer.toHexString() 方法的引用,沒(méi)有其它代碼,因此我們可以直接把方法作為參數(shù)傳遞,由編譯器幫我們處理,這就是靜態(tài)方法引用:

② 類的非靜態(tài)方法引用

接下來(lái),我們把剛剛生成的 String 集合 hexList 中的元素都變成大寫,需要借助于String類的toUpperCase()方法:

這次是非靜態(tài)方法,不能用類名調(diào)用,需要用實(shí)例對(duì)象,因此與剛剛的實(shí)現(xiàn)有一些差別,我們接收集合中的每一個(gè)字符串 s 。但與上面不同然后 s 不是 toUpperCase() 的參數(shù),而是調(diào)用者:

因?yàn)榇a體只有對(duì) toUpperCase() 的調(diào)用,所以可以把方法作為參數(shù)引用傳遞,依然可以簡(jiǎn)寫:

③ 指定實(shí)例的非靜態(tài)方法引用

下面一個(gè)需求是這樣的,我們先定義一個(gè)數(shù)字 Integer num = 2000 ,然后用這個(gè)數(shù)字和集合中的每個(gè)數(shù)字進(jìn)行比較,比較的結(jié)果放入一個(gè)新的集合。比較對(duì)象,我們可以用 Integer 的 compareTo 方法:

先用Lambda實(shí)現(xiàn)。

與前面類似,這里L(fēng)ambda的代碼塊中,依然只有對(duì) num.compareTo(i) 的調(diào)用,所以可以簡(jiǎn)寫。但是,需要注意的是,這次方法的調(diào)用者不是集合的元素,而是一個(gè)外部的局部變量 num ,因此不能使用Integer::compareTo ,因?yàn)檫@樣是無(wú)法確定方法的調(diào)用者。要指定調(diào)用者,需要用 對(duì)象::方法名 的方式:

④ 構(gòu)造函數(shù)引用

最后一個(gè)場(chǎng)景:把集合中的數(shù)字作為毫秒值,構(gòu)建出 Date 對(duì)象并放入集合,這里我們就需要用到Date的構(gòu)造函數(shù):

我們可以接收集合中的每個(gè)元素,然后把元素作為 Date 的構(gòu)造函數(shù)參數(shù):

上面的Lambda表達(dá)式實(shí)現(xiàn)方式,代碼體只有 new Date() 一行代碼,因此也可以采用方法引用進(jìn)行簡(jiǎn)寫。但問(wèn)題是,構(gòu)造函數(shù)沒(méi)有名稱,我們只能用 new 關(guān)鍵字來(lái)代替:

注意兩點(diǎn):

· 上面代碼中的System.out::println 其實(shí)是 指定對(duì)象System.out的非靜態(tài)方法println的引用

· 如果構(gòu)造函數(shù)有多個(gè),可能無(wú)法區(qū)分導(dǎo)致傳遞失敗

接口的默認(rèn)方法和靜態(tài)方法

Java 8使用兩個(gè)新概念擴(kuò)展了接口的含義:默認(rèn)方法和靜態(tài)方法。

⒈默認(rèn)方法

默認(rèn)方法使得開發(fā)者可以在 不破壞二進(jìn)制兼容性的前提下,往現(xiàn)存接口中添加新的方法,即不強(qiáng)制那些實(shí)現(xiàn)了該接口的類也同時(shí)實(shí)現(xiàn)這個(gè)新加的方法。

默認(rèn)方法和抽象方法之間的區(qū)別在于抽象方法需要實(shí)現(xiàn),而默認(rèn)方法不需要。接口提供的默認(rèn)方法會(huì)被接口的實(shí)現(xiàn)類繼承或者覆寫,例子代碼如下:

點(diǎn)擊添加圖片描述(最多60個(gè)字)

Defaulable接口使用關(guān)鍵字default定義了一個(gè)默認(rèn)方法notRequired()。DefaultableImpl類實(shí)現(xiàn)了這個(gè)接口,同時(shí)默認(rèn)繼承了這個(gè)接口中的默認(rèn)方法;OverridableImpl類也實(shí)現(xiàn)了這個(gè)接口,但覆寫了該接口的默認(rèn)方法,并提供了一個(gè)不同的實(shí)現(xiàn)。

⒉ 靜態(tài)方法

Java 8帶來(lái)的另一個(gè)有趣的特性是在接口中可以定義靜態(tài)方法,我們可以直接用接口調(diào)用這些靜態(tài)方法。例子代碼如下:

下面的代碼片段整合了默認(rèn)方法和靜態(tài)方法的使用場(chǎng)景:

這段代碼的輸出結(jié)果如下:

由于JVM上的默認(rèn)方法的實(shí)現(xiàn)在字節(jié)碼層面提供了支持,因此效率非常高。默認(rèn)方法允許在不打破現(xiàn)有繼承體系的基礎(chǔ)上改進(jìn)接口。該特性在官方庫(kù)中的應(yīng)用是:給 java.util.Collection 接口添加新方法,如 stream() 、

parallelStream() 、 forEach() 和 removeIf() 等等。

盡管默認(rèn)方法有這么多好處,但在實(shí)際開發(fā)中應(yīng)該謹(jǐn)慎使用:在復(fù)雜的繼承體系中,默認(rèn)方法可能引起歧義和編譯錯(cuò)誤。如果你想了解更多細(xì)節(jié),可以參考官方文檔。

Optional

Java應(yīng)用中最常見(jiàn)的bug就是空值異常。

Optional 僅僅是一個(gè)容器,可以存放T類型的值或者 null 。它提供了一些有用的接口來(lái)避免顯式的 null 檢查,可以參考Java 8官方文檔了解更多細(xì)節(jié)。

接下來(lái)看一點(diǎn)使用Optional的例子:可能為空的值或者某個(gè)類型的值:

如果 Optional 實(shí)例持有一個(gè)非空值,則 isPresent() 方法返回 true ,否則返回 false ;如果 Optional 實(shí)例持有null , orElseGet() 方法可以接受一個(gè)lambda表達(dá)式生成的默認(rèn)值;map() 方法可以將現(xiàn)有的 Optional 實(shí)例的值轉(zhuǎn)換成新的值;orElse() 方法與 orElseGet() 方法類似,但是在持有null的時(shí)候返回傳入的默認(rèn)值,而不是通過(guò)Lambda來(lái)生成。

上述代碼的輸出結(jié)果如下:

再看下另一個(gè)簡(jiǎn)單的例子:

這個(gè)例子的輸出是:

Streams

新增的Stream API(java.util.stream)將生成環(huán)境的函數(shù)式編程引入了Java庫(kù)中。這是目前為止最大的一次對(duì)Java庫(kù)的完善,以便開發(fā)者能夠?qū)懗龈佑行?、更加?jiǎn)潔和緊湊的代碼。

Steam API極大得簡(jiǎn)化了集合操作(后面我們會(huì)看到不止是集合),首先看下這個(gè)叫Task的類:

Task類有一個(gè)points屬性,另外還有兩種狀態(tài):OPEN或者CLOSED?,F(xiàn)在假設(shè)有一個(gè)task集合:

首先看一個(gè)問(wèn)題:在這個(gè)task集合中一共有多少個(gè)OPEN狀態(tài)的?計(jì)算出它們的points屬性和。在Java 8之前,要解決這個(gè)問(wèn)題,則需要使用foreach循環(huán)遍歷task集合;但是在Java 8中可以利用steams解決:包括一系列元素的列表,并且支持順序和并行處理。

運(yùn)行這個(gè)方法的控制臺(tái)輸出是:

這里有很多知識(shí)點(diǎn)值得說(shuō)。首先, tasks 集合被轉(zhuǎn)換成 steam 表示;其次,在 steam 上的 filter 操作會(huì)過(guò)濾掉所有CLOSED 的 task ;第三, mapToInt 操作基于 tasks 集合中的每個(gè) task 實(shí)例的 Task::getPoints 方法將 task流轉(zhuǎn)換成 Integer 集合;最后,通過(guò) sum 方法計(jì)算總和,得出最后的結(jié)果。

在學(xué)習(xí)下一個(gè)例子之前,還需要記住一些steams(點(diǎn)此更多細(xì)節(jié))的知識(shí)點(diǎn)。Steam之上的操作可分為中間操作和晚期操作。

中間操作會(huì)返回一個(gè)新的steam——執(zhí)行一個(gè)中間操作(例如filter)并不會(huì)執(zhí)行實(shí)際的過(guò)濾操作,而是創(chuàng)建一個(gè)新的steam,并將原steam中符合條件的元素放入新創(chuàng)建的steam。

晚期操作(例如forEach或者sum),會(huì)遍歷steam并得出結(jié)果或者附帶結(jié)果;在執(zhí)行晚期操作之后,steam處理線已經(jīng)處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對(duì)steam進(jìn)行遍歷。

steam的另一個(gè)價(jià)值是創(chuàng)造性地支持并行處理(parallel processing)。對(duì)于上述的tasks集合,我們可以用下面的代碼計(jì)算所有task的points之和:

這里我們使用parallel方法并行處理所有的task,并使用reduce方法計(jì)算最終的結(jié)果。控制臺(tái)輸出如下:

對(duì)于一個(gè)集合,經(jīng)常需要根據(jù)某些條件對(duì)其中的元素分組。利用steam提供的API可以很快完成這類任務(wù),代碼如下:

控制臺(tái)的輸出如下:

最后一個(gè)關(guān)于tasks集合的例子問(wèn)題是:如何計(jì)算集合中每個(gè)任務(wù)的點(diǎn)數(shù)在集合中所占的比重,具體處理的代碼如下:

控制臺(tái)輸出結(jié)果如下:

最后,正如之前所說(shuō),Steam API不僅可以作用于Java集合,傳統(tǒng)的IO操作(從文件或者網(wǎng)絡(luò)一行一行得讀取數(shù)據(jù))可以受益于steam處理,這里有一個(gè)小例子:

Stream的方法 onClose() 返回一個(gè)等價(jià)的有額外句柄的Stream,當(dāng)Stream的 close() 方法被調(diào)用的時(shí)候這個(gè)句柄會(huì)被執(zhí)行。Stream API、Lambda表達(dá)式還有接口默認(rèn)方法和靜態(tài)方法支持的方法引用,是Java 8對(duì)軟件開發(fā)的現(xiàn)代范式的響應(yīng)。


并行數(shù)組

Java8版本新增了很多新的方法,用于支持并行數(shù)組處理。最重要的方法是 parallelSort() ,可以顯著加快多核機(jī)器上的數(shù)組排序。下面的例子論證了parallexXxx系列的方法:


上述這些代碼使用parallelSetAll()方法生成20000個(gè)隨機(jī)數(shù),然后使用parallelSort()方法進(jìn)行排序。這個(gè)程序會(huì)輸出亂序數(shù)組和排序數(shù)組的前10個(gè)元素。上述例子的代碼輸出的結(jié)果是:



0 分享到:
和我們?cè)诰€交談!