天天看點

Java 8新特性

編者注:java 8已經公布有一段時間了,種種迹象表明java 8是一個有重大改變的發行版。

現在,是時候把所有java 8的重要特性收集整理成一篇單獨的文章了,希望這篇文章能給你帶來閱讀上的樂趣。開始吧!

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#intro">介紹</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#newfeatureoflanguage">java語言的新特性</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#newfeatureofcompiler">java編譯器的新特性</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#newfeatureoflibraries">java 類庫的新特性</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#newjavatool">新增的java工具</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#newfeatureofjvm">java虛拟機(jvm)的新特性</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#conclusions">總結</a>

<a target="_blank" href="http://www.open-open.com/lib/view/open1403232177575.html#resources">更多資源</a>

<a target="_blank"></a>

這篇教程由以下幾部分組成,它們分别涉及到java平台某一特定方面的内容:

java語言

編譯器

類庫

工具

java運作時(jvm)

不管怎麼說,java 8都是一個變化巨大的版本。你可能認為java 8耗費了大量的時間才得以完成是為了實作了每個java程式員所期待的特性。在這個小節裡,我們将會涉及到這些特性的大部分。

關于lambda設計的讨論占用了大量的時間與社群的努力。可喜的是,最終找到了一個平衡點,使得可以使用一種即簡潔又緊湊的新方式來構造lambdas。在最簡單的形式中,一個lambda可以由用逗号分隔的參數清單、–&gt;符号與函數體三部分表示。例如:

請注意參數e的類型是由編譯器推測出來的。同時,你也可以通過把參數類型與參數包括在括号中的形式直接給出參數的類型:

在某些情況下lambda的函數體會更加複雜,這時可以把函數體放到在一對花括号中,就像在java中定義普通函數一樣。例如:

lambda可以引用類的成員變量與局部變量(如果這些變量不是final的話,它們會被隐含的轉為final,這樣效率更高)。例如,下面兩個代碼片段是等價的:

和:

lambda可能會傳回一個值。傳回值的類型也是由編譯器推測出來的。如果lambda的函數體隻有一行的話,那麼沒有必要顯式使用return語句。下面兩個代碼片段是等價的:

語言設計者投入了大量精力來思考如何使現有的函數友好地支援lambda。最終采取的方法是:增加函數式接口的概念。函數式接口就是一個具有一個方法的普通接口。像這樣的接口,可以被隐式轉換為lambda表達式。java.lang.runnable與java.util.concurrent.callable是函數式接口最典型的兩個例子。在實際使用過程中,函數式接口是容易出錯的:如有某個人在接口定義中增加了另一個方法,這時,這個接口就不再是函數式的了,并且編譯過程也會失敗。為了克服函數式接口的這種脆弱性并且能夠明确聲明接口作為函數式接口的意圖,java 8增加了一種特殊的注解@functionalinterface(java 8中所有類庫的已有接口都添加了@functionalinterface注解)。讓我們看一下這種函數式接口的定義:

預設方法與抽象方法不同之處在于抽象方法必須要求實作,但是預設方法則沒有這個要求。相反,每個接口都必須提供一個所謂的預設實作,這樣所有的接口實作者将會預設繼承它(如果有必要的話,可以覆寫這個預設實作)。讓我們看看下面的例子:

defaulable接口用關鍵字default聲明了一個預設方法notrequired(),defaulable接口的實作者之一defaultableimpl實作了這個接口,并且讓預設方法保持原樣。defaulable接口的另一個實作者overridableimpl用自己的方法覆寫了預設方法。

java 8帶來的另一個有趣的特性是接口可以聲明(并且可以提供實作)靜态方法。例如:

下面的一小段代碼片段把上面的預設方法與靜态方法黏合到一起。

這個程式的控制台輸出如下:

在jvm中,預設方法的實作是非常高效的,并且通過位元組碼指令為方法調用提供了支援。預設方法允許繼續使用現有的java接口,而同時能夠保障正常的編譯過程。這方面好的例子是大量的方法被添加到java.util.collection接口中去:stream(),parallelstream(),foreach(),removeif(),……

方法引用提供了非常有用的文法,可以直接引用已有java類或對象(執行個體)的方法或構造器。與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少備援代碼。

下面,我們以定義了4個方法的car這個類作為例子,區分java中支援的4種不同的方法引用。

第一種方法引用是構造器引用,它的文法是class::new,或者更一般的class&lt; t &gt;::new。請注意構造器沒有參數。

第二種方法引用是靜态方法引用,它的文法是class::static_method。請注意這個方法接受一個car類型的參數。

第三種方法引用是特定類的任意對象的方法引用,它的文法是class::method。請注意,這個方法沒有參數。

最後,第四種方法引用是特定對象的方法引用,它的文法是instance::method。請注意,這個方法接受一個car類型的參數

運作上面的java程式在控制台上會有下面的輸出(car的執行個體可能不一樣):

重複注解機制本身必須用@repeatable注解。事實上,這并不是語言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。讓我們看一個快速入門的例子:

正如我們看到的,這裡有個使用@repeatable( filters.class )注解的注解類filter,filters僅僅是filter注解的數組,但java編譯器并不想讓程式員意識到filters的存在。這樣,接口filterable就擁有了兩次filter(并沒有提到filter)注解。

同時,反射相關的api提供了新的函數getannotationsbytype()來傳回重複注解的類型(請注意filterable.class.getannotation( filters.class )經編譯器處理後将會傳回filters的執行個體)。

程式輸出結果如下:

java 8在類型推測方面有了很大的提高。在很多情況下,編譯器可以推測出确定的參數類型,這樣就能使代碼更整潔。讓我們看一個例子:

這裡是value&lt; string &gt;類型的用法。

value.defaultvalue()的參數類型可以被推測出,是以就不必明确給出。在java 7中,相同的例子将不會通過編譯,正确的書寫方式是 value.&lt; string &gt;defaultvalue()。

java 8擴充了注解的上下文。現在幾乎可以為任何東西添加注解:局部變量、泛型類、父類與接口的實作,就連方法的異常也能添加注解。下面示範幾個例子:

elementtype.type_use和elementtype.type_parameter是兩個新添加的用于描述适當的注解上下文的元素類型。在java語言中,注解處理api也有小的改動來識别新增的類型注解。

如果不使用–parameters參數來編譯這個類,然後運作這個類,會得到下面的輸出:

如果使用–parameters參數來編譯這個類,程式的結構會有所不同(參數的真實名字将會顯示出來):

Java 8新特性

圖1. 配置eclipse工程使之支援java 8編譯器的新特性——parameters參數

此外,parameter類有一個很友善的方法isnamepresent()來驗證是否可以擷取參數的名字。

java 8 通過增加大量新類,擴充已有類的功能的方式來改善對并發程式設計、函數式程式設計、日期/時間相關操作以及其他更多方面的支援。

我們下面用兩個小例子來示範如何使用optional類:一個允許為空值,一個不允許為空值。

如果optional類的執行個體為非空值的話,ispresent()傳回true,否從傳回false。為了防止optional為空值,orelseget()方法通過回調函數來産生一個預設值。map()函數對目前optional的值進行轉化,然後傳回一個新的optional執行個體。orelse()方法和orelseget()方法類似,但是orelse接受一個預設值而不是一個回調函數。下面是這個程式的輸出:

讓我們來看看另一個例子:

下面是程式的輸出:

stream api極大簡化了集合架構的處理(但它的處理的範圍不僅僅限于集合架構的處理,這點後面我們會看到)。讓我們以一個簡單的task類為例進行介紹:

task類有一個分數的概念(或者說是僞複雜度),其次是還有一個值可以為open或closed的狀态.讓我們引入一個task的小集合作為示範例子:

我們下面要讨論的第一個問題是所有狀态為open的任務一共有多少分數?在java 8以前,一般的解決方式用foreach循環,但是在java 8裡面我們可以使用stream:一串支援連續、并行聚集操作的元素。

程式在控制台上的輸出如下:

這裡有幾個注意事項。第一,task集合被轉換化為其相應的stream表示。然後,filter操作過濾掉狀态為closed的task。下一步,maptoint操作通過task::getpoints這種方式調用每個task執行個體的getpoints方法把task的stream轉化為integer的stream。最後,用sum函數把所有的分數加起來,得到最終的結果。

中間操作傳回一個新的stream對象。中間操作總是采用惰性求值方式,運作一個像filter這樣的中間操作實際上沒有進行任何過濾,相反它在周遊元素時會産生了一個新的stream對象,這個新的stream對象包含原始stream

中符合給定謂詞的所有元素。

像foreach、sum這樣的最終操作可能直接周遊stream,産生一個結果或副作用。當最終操作執行結束之後,stream管道被認為已經被消耗了,沒有可能再被使用了。在大多數情況下,最終操作都是采用及早求值方式,及早完成底層資料源的周遊。

stream另一個有價值的地方是能夠原生支援并行處理。讓我們來看看這個算task分數和的例子。

這個例子和第一個例子很相似,但這個例子的不同之處在于這個程式是并行運作的,其次使用reduce方法來算最終的結果。

下面是這個例子在控制台的輸出:

經常會有這個一個需求:我們需要按照某種準則來對集合中的元素進行分組。stream也可以處理這樣的需求,下面是一個例子:

這個例子的控制台輸出如下:

讓我們來計算整個集合中每個task分數(或權重)的平均值來結束task的例子。

下面是這個例子的控制台輸出:

最後,就像前面提到的,stream api不僅僅處理java集合架構。像從文本檔案中逐行讀取資料這樣典型的i/o操作也很适合用stream api來處理。下面用一個例子來應證這一點。

對一個stream對象調用onclose方法會傳回一個在原有功能基礎上新增了關閉功能的stream對象,當對stream對象調用close()方法時,與關閉相關的處理器就會執行。

讓我們用例子來看一下新版api主要類的使用方法。第一個是clock類,它通過指定一個時區,然後就可以擷取到目前的時刻,日期與時間。clock可以替換system.currenttimemillis()與timezone.getdefault()。

下面是程式在控制台上的輸出:

我們需要關注的其他類是localedate與localtime。localedate隻持有iso-8601格式且無時區資訊的日期部分。相應的,localetime隻持有iso-8601格式且無時區資訊的時間部分。localedate與localtime都可以從clock中得到。

如果你需要特定時區的日期/時間,那麼zoneddatetime是你的選擇。它持有iso-8601格式具具有時區資訊的日期與時間。下面是一些不同時區的例子:

最後,讓我們看一下duration類:在秒與納秒級别上的一段時間。duration使計算兩個日期間的不同變的十分簡單。下面讓我們看一個這方面的例子。

上面的例子計算了兩個日期2014年4月16号與2014年4月16号之間的過程。下面是程式在控制台上的輸出:

nashorn,一個新的javascript引擎随着java 8一起公諸于世,它允許在jvm上開發運作某些javascript應用。nashorn就是javax.script.scriptengine的另一種實作,并且它們倆遵循相同的規則,允許java與javascript互相調用。下面看一個例子:

程式在控制台上輸出了編碼後的字元與解碼後的字元:

base64類同時還提供了對url、mime友好的編碼器與解碼器(base64.geturlencoder() / base64.geturldecoder(), base64.getmimeencoder() / base64.getmimedecoder())。

java 8增加了大量的新方法來對數組進行并行處理。可以說,最重要的是parallelsort()方法,因為它可以在多核機器上極大提高數組排序的速度。下面的例子展示了新方法(parallelxxx)的使用。

上面的代碼片段使用了parallelsetall()方法來對一個有20000個元素的數組進行随機指派。然後,調用parallelsort方法。這個程式首先列印出前10個元素的值,之後對整個數組排序。這個程式在控制台上的輸出如下(請注意數組元素是随機生産的):

新增的java.util.concurrent.locks.stampedlock類提供一直基于容量的鎖,這種鎖有三個模型來控制讀寫操作(它被認為是不太有名的java.util.concurrent.locks.readwritelock類的替代者)。

在java.util.concurrent.atomic包中還增加了下面這些類:

doubleaccumulator

doubleadder

longaccumulator

longadder

java 8也帶來了一些新的指令行工具。在這節裡我們将會介紹它們中最有趣的部分。

jjs是個基于nashorn引擎的指令行工具。它接受一些javascript源代碼為參數,并且執行這些源代碼。例如,我們建立一個具有如下内容的func.js檔案:

我們可以把這個檔案作為參數傳遞給jjs使得這個檔案可以在指令行中執行:

jdeps是一個很有用的指令行工具。它可以顯示java類的包級别或類級别的依賴。它接受一個.class檔案,一個目錄,或者一個jar檔案作為輸入。jdeps預設把結果輸出到系統輸出(控制台)上。

這個指令輸出的内容很多,是以這裡我們隻選取一小部分。依賴資訊按照包名進行分組。如果依賴不在classpath中,那麼就會顯示not found。

更多展望:java 8通過釋出一些可以增加程式員生産力的特性來推進這個偉大的平台的進步。現在把生産環境遷移到java 8還為時尚早,但是在接下來的幾個月裡,它會被大衆慢慢的接受。毫無疑問,現在是時候讓你的代碼與java 8相容,并且在java 8足夠安全穩定的時候遷移到java 8。

我們歡迎你對java 8中激動人心的特性進行評論!