1.簡介javascript
毫無疑問,Java 8是自Java 5(2004年)發佈以來Java語言最大的一次版本升級,Java 8帶來了不少的新特性,好比編譯器、類庫、開發工具和JVM(Java虛擬機)。在這篇教程中咱們將會學習這些新特性,並經過真實例子演示說明它們適用的場景。html
本教程由下面幾部分組成,它們分別涉及到Java平臺某一特定方面的內容:java
2.Java的新特性程序員
整體來講,Java 8是一個大的版本升級。有人可能會說,Java 8的新特性很是使人期待,可是也要花費大量的時間去學習。這一節咱們會講到這些新特性。spring
2.1 Lambda表達式和函數式接口express
Lambda表達式(也叫作閉包)是Java 8中最大的也是期待已久的變化。它容許咱們將一個函數看成方法的參數(傳遞函數),或者說把代碼看成數據,這是每一個函數式編程者熟悉的概念。不少基於JVM平臺的語言一開始就支持Lambda表達式,可是Java程序員沒有選擇,只能使用匿名內部類來替代Lambda表達式。apache
Lambda表達式的設計被討論了好久,並且花費了不少的功夫來交流。不過最後取得了一個折中的辦法,獲得了一個新的簡明而且緊湊的Lambda表達式結構。最簡單的Lambda表達式能夠用逗號分隔的參數列表、->符號和功能語句塊來表示。示例以下:編程
1api |
|
請注意到編譯器會根據上下文來推測參數的類型,或者你也能夠顯示地指定參數類型,只須要將類型包在括號裏。舉個例子:
1 |
|
若是Lambda的功能語句塊太複雜,咱們能夠用大括號包起來,跟普通的Java方法同樣,以下:
1 2 3 |
|
Lambda表達式可能會引用類的成員或者局部變量(會被隱式地轉變成final類型),下面兩種寫法的效果是同樣的:
1 2 3 |
|
和
1 2 3 |
|
Lambda表達式可能會有返回值,編譯器會根據上下文推斷返回值的類型。若是lambda的語句塊只有一行,不須要return關鍵字。下面兩個寫法是等價的:
1 |
|
和
1 2 3 4 |
|
語言的設計者們思考了不少如何讓現有的功能和lambda表達式友好兼容。因而就有了函數接口這個概念。函數接口是一種只有一個方法的接口,像這樣地,函數接口能夠隱式地轉換成lambda表達式。
java.lang.Runnable 和java.util.concurrent.Callable是函數接口兩個最好的例子。可是在實踐中,函數接口是很是脆弱的,只要有人在接口裏添加多一個方法,那麼這個接口就不是函數接口了,就會致使編譯失敗。Java 8提供了一個特殊的註解@FunctionalInterface來克服上面提到的脆弱性而且顯示地代表函數接口的目的(java裏全部現存的接口都已經加上了@FunctionalInterface)。讓咱們看看一個簡單的函數接口定義:
1 2 3 4 |
|
咱們要記住默認的方法和靜態方法(下一節會具體解釋)不會違反函數接口的約定,例子以下:
1 2 3 4 5 6 7 |
|
支持Lambda是Java 8最大的賣點,他有巨大的潛力吸引愈來愈多的開發人員轉到這個開發平臺來,而且在純Java裏提供最新的函數式編程的概念。對於更多的細節,請參考官方文檔。
2.2 接口的默認方法和靜態方法
Java 8增長了兩個新的概念在接口聲明的時候:默認和靜態方法。默認方法和Trait有些相似,可是目標不同。默認方法容許咱們在接口裏添加新的方法,而不會破壞實現這個接口的已有類的兼容性,也就是說不會強迫實現接口的類實現默認方法。
默認方法和抽象方法的區別是抽象方法必需要被實現,默認方法不是。做爲替代方式,接口能夠提供一個默認的方法實現,全部這個接口的實現類都會經過繼承得倒這個方法(若是有須要也能夠重寫這個方法),讓咱們來看看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
接口Defaulable使用default關鍵字聲明瞭一個默認方法notRequired(),類DefaultableImpl實現了Defaulable接口,沒有對默認方法作任何修改。另一個類OverridableImpl重寫類默認實現,提供了本身的實現方法。
Java 8 的另一個有意思的新特性是接口裏能夠聲明靜態方法,而且能夠實現。例子以下:
1 2 3 4 5 6 |
|
下面是把接口的靜態方法和默認方法放在一塊兒的示例(::new 是構造方法引用,後面會有詳細描述):
1 2 3 4 5 6 7 |
|
控制檯的輸出以下:
Default implementation
Overridden implementation
JVM平臺的接口的默認方法實現是很高效的,而且方法調用的字節碼指令支持默認方法。默認方法使已經存在的接口能夠修改而不會影響編譯的過程。java.util.Collection中添加的額外方法就是最好的例子:stream(), parallelStream(), forEach(), removeIf()
雖然默認方法很強大,可是使用以前必定要仔細考慮是否是真的須要使用默認方法,由於在層級很複雜的狀況下很容易引發模糊不清甚至變異錯誤。更多的詳細信息請參考官方文檔。
2.3 方法引用
方法引用提供了一個頗有用的語義來直接訪問類或者實例的已經存在的方法或者構造方法。結合Lambda表達式,方法引用使語法結構緊湊簡明。不須要複雜的引用。
下面咱們用Car 這個類來作示例,Car這個類有不一樣的方法定義。讓咱們來看看java 8支持的4種方法引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
第一種方法引用是構造方法引用,語法是:Class::new ,對於泛型來講語法是:Class<T >::new,請注意構造方法沒有參數:
1 2 |
|
第二種方法引用是靜態方法引用,語法是:Class::static_method請注意這個靜態方法只支持一個類型爲Car的參數。
1 |
|
第三種方法引用是類實例的方法引用,語法是:Class::method請注意方法沒有參數。
1 |
|
最後一種方法引用是引用特殊類的方法,語法是:instance::method,請注意只接受Car類型的一個參數。
1 2 |
|
運行這些例子咱們將會在控制檯獲得以下信息(Car的實例可能會不同):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
關於方法引用更多的示例和詳細信息,請參考官方文檔
2.4 重複註釋
自從Java 5支持註釋以來,註釋變得特別受歡迎於是被普遍使用。可是有一個限制,同一個地方的不能使用同一個註釋超過一次。 Java 8打破了這個規則,引入了重複註釋,容許相同註釋在聲明使用的時候重複使用超過一次。
重複註釋自己須要被@Repeatable註釋。實際上,他不是一個語言上的改變,只是編譯器層面的改動,技術層面仍然是同樣的。讓咱們來看看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
咱們能夠看到,註釋Filter被@Repeatable( Filters.class )註釋。Filters 只是一個容器,它持有Filter, 編譯器盡力向程序員隱藏它的存在。經過這樣的方式,Filterable接口能夠被Filter註釋兩次。
另外,反射的API提供一個新方法getAnnotationsByType() 來返回重複註釋的類型(請注意Filterable.class.getAnnotation( Filters.class )將會返回編譯器注入的Filters實例)。
程序的輸出將會是這樣:
filter1
filter2
更多詳細信息請參考官方文檔。
2.5 更好的類型推斷
Java 8在類型推斷方面改進了不少,在不少狀況下,編譯器能夠推斷參數的類型,從而保持代碼的整潔。讓咱們看看例子:
package com.javacodegeeks.java8.type.inference;
1 2 3 4 5 6 7 8 9 10 11 |
|
這裏是Value< String >的用法
1 2 3 4 5 6 7 8 |
|
參數Value.defaultValue()的類型被編譯器推斷出來,不須要顯式地提供類型。在java 7, 相同的代碼不會被編譯,須要寫成:Value.< String >defaultValue()
2.6 註解的擴展
Java 8擴展了註解可使用的範圍,如今咱們幾乎能夠在全部的地方:局部變量、泛型、超類和接口實現、甚至是方法的Exception聲明。一些例子以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Java 8 新增長了兩個註解的程序元素類型ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER ,這兩個新類型描述了可使用註解的新場合。註解處理API(Annotation Processing API)也作了一些細微的改動,來識別這些新添加的註解類型。
3.Java編譯器的新特性
3.1 參數名字
很長時間以來,Java程序員想盡辦法把參數名字保存在java字節碼裏,而且讓這些參數名字在運行時可用。Java 8 終於把這個需求加入到了Java語言(使用反射API和Parameter.getName() 方法)和字節碼裏(使用java編譯命令javac的–parameters參數)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
若是你編譯這個class的時候沒有添加參數–parameters,運行的時候你會獲得這個結果:
Parameter: arg0
編譯的時候添加了–parameters參數的話,運行結果會不同:
Parameter: args
對於有經驗的Maven使用者,–parameters參數能夠添加到maven-compiler-plugin的配置部分:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
最新版的Eclipse Kepler SR2 提供了編譯設置項,以下圖所示:
Picture 1. Configuring Eclipse projects to support new Java 8 compiler –parameters argument.
額外的,有一個方便的方法Parameter.isNamePresent() 來驗證參數名是否是可用。
4.Java 庫的新特性
Java 8 新添加了不少類,而且擴展了不少現有的類來更好地支持現代併發、函數式編程、日期\時間等等。
4.1 Optional
著名的NullPointerException 是引發系統失敗最多見的緣由。好久之前Google Guava項目引入了Optional做爲解決空指針異常的一種方式,不同意代碼被null檢查的代碼污染,指望程序員寫整潔的代碼。受Google Guava的鼓勵,Optional 如今是Java 8庫的一部分。
Optional 只是一個容器,它能夠保存一些類型的值或者null。它提供不少有用的方法,因此沒有理由不顯式地檢查null。請參照java 8的文檔查看詳細信息。
讓咱們看看兩個Optional 用法的小例子:一個是容許爲空的值,另一個是不容許爲空的值。
1 2 3 4 |
|
若是Optional實例有非空的值,方法 isPresent() 返回true不然返回false。方法orElseGet提供了回退機制,當Optional的值爲空時接受一個方法返回默認值。map()方法轉化Optional當前的值而且返回一個新的Optional實例。orElse方法和orElseGet相似,可是它不接受一個方法,而是接受一個默認值。上面代碼運行結果以下:
Full Name is set? false
Full Name: [none]
Hey Stranger!
讓咱們大概看看另一個例子。
1 2 3 4 5 |
|
輸出以下:
First Name is set? true
First Name: Tom
Hey Tom!
更多詳細信息請參考官方文檔。
4.2 Stream
新增長的Stream API (java.util.stream)引入了在Java裏能夠工做的函數式編程。這是目前爲止對java庫最大的一次功能添加,但願程序員經過編寫有效、整潔和簡明的代碼,可以大大提升生產率。
Stream API讓集合處理簡化了不少(咱們後面會看到不只限於Java集合類)。讓咱們從一個簡單的類Task開始來看看Stream的用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Task類有一個分數的概念(或者說是僞複雜度),其次是還有一個值能夠爲OPEN或CLOSED的狀態.讓咱們引入一個Task的小集合做爲演示例子:
1 2 3 4 5 |
|
第一個問題是全部的開放的Task的點數是多少?在java 8 以前,一般的作法是用foreach迭代。可是Java8裏頭咱們會用Stream。Stream是多個元素的序列,支持串行和並行操做。
1 2 3 4 5 6 7 8 |
|
控制檯的輸出將會是:
Total points: 18
上面代碼執行的流程是這樣的,首先Task集合會被轉化爲Stream表示,而後filter操做會過濾掉全部關閉的Task,接下來使用Task::getPoints 方法取得每一個Task實例的點數,mapToInt方法會把Task Stream轉換成Integer Stream,最後使用Sum方法將全部的點數加起來獲得最終的結果。
在咱們看下一個例子以前,咱們要記住一些關於Stream的說明。Stream操做被分爲中間操做和終點操做。
中間操做返回一個新的Stream。這些中間操做是延遲的,執行一箇中間操做好比filter實際上不會真的作過濾操做,而是建立一個新的Stream,當這個新的Stream被遍歷的時候,它裏頭會包含有原來Stream裏符合過濾條件的元素。
終點操做好比說forEach或者sum會遍歷Stream從而產生最終結果或附帶結果。終點操做執行完以後,Stream管道就被消費完了,再也不可用。在幾乎全部的狀況下,終點操做都是即時完成對數據的遍歷操做。
Stream的另一個價值是Stream創造性地支持並行處理。讓咱們看看下面這個例子,這個例子把全部task的點數加起來。
1 2 3 4 5 6 7 8 |
|
這個例子跟上面那個很是像,除了這個例子裏使用了parallel()方法 而且計算最終結果的時候使用了reduce方法。
輸出以下:
Total points (all tasks): 26.0
常常會有這個一個需求:咱們須要按照某種準則來對集合中的元素進行分組。Stream也能夠處理這樣的需求,下面是一個例子:
1 2 3 4 5 |
|
控制檯的輸出以下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
讓咱們來計算整個集合中每一個task分數(或權重)的平均值來結束task的例子。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
控制檯輸出以下:
[19%, 50%, 30%]
最後,就像前面提到的,Stream API不只僅處理Java集合框架。像從文本文件中逐行讀取數據這樣典型的I/O操做也很適合用Stream API來處理。下面用一個例子來應證這一點。
1 2 3 4 |
|
Stream的方法onClose 返回一個等價的有額外句柄的Stream,當Stream的close()方法被調用的時候這個句柄會被執行。
Stream API、Lambda表達式還有接口默認方法和靜態方法支持的方法引用,是Java 8對軟件開發的現代範式的響應。
4.3日期時間API(JSR310)
Java 8引入了新的日期時間API(JSR 310)改進了日期時間的管理。日期和時間管理一直是Java開發人員最痛苦的問題。java.util.Date和後來的java.util.Calendar一點也沒有改變這個狀況(甚至讓人們更加迷茫)。
由於上面這些緣由,產生了Joda-Time ,能夠替換Java的日期時間API。Joda-Time深入影響了 Java 8新的日期時間API,Java 8吸取了Joda-Time 的精華。新的java.time包包含了全部關於日期、時間、日期時間、時區、Instant(跟日期相似但精確到納秒)、duration(持續時間)和時鐘操做的類。設計這些API的時候很認真地考慮了這些類的不變性(從java.util.Calendar吸收的痛苦教訓)。若是須要修改時間對象,會返回一個新的實例。
讓咱們看看一些關鍵的類和用法示例。第一個類是Clock,Clock使用時區來訪問當前的instant, date和time。Clock類能夠替換 System.currentTimeMillis() 和 TimeZone.getDefault().
1 2 3 4 |
|
控制檯輸出以下:
2014-04-12T15:19:29.282Z
1397315969360
其餘類咱們看看LocalTime和LocalDate。LocalDate只保存有ISO-8601日期系統的日期部分,有時區信息,相應地,LocalTime只保存ISO-8601日期系統的時間部分,沒有時區信息。LocalDate和LocalTime均可以從Clock對象建立。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
控制檯輸出以下:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime類合併了LocalDate和LocalTime,它保存有ISO-8601日期系統的日期和時間,可是沒有時區信息。讓咱們看一個簡單的例子。
1 2 3 4 5 6 |
|
輸出以下:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
若是您須要一個類持有日期時間和時區信息,可使用ZonedDateTime,它保存有ISO-8601日期系統的日期和時間,並且有時區信息。讓咱們看一些例子:
1 2 3 4 5 6 7 8 |
|
輸出以下:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
最後讓咱們看看Duration類,Duration持有的時間精確到納秒。它讓咱們很容易計算兩個日期中間的差別。讓咱們來看一下:
1 2 3 4 5 6 7 |
|
上面的例子計算了兩個日期(2014年4月16日和2014年5月16日)之間的持續時間(基於天數和小時)輸出以下:
Duration in days: 365
Duration in hours: 8783
對於Java 8的新日期時間的整體印象仍是比較積極的。一部分是由於有經歷實戰的Joda-Time的基礎,還有一部分是由於日期時間終於被認真對待並且聽取了開發人員的聲音。關於更多的詳細信息,請參考官方文檔。
4.4 Nashorn javascript引擎
Java 8提供了一個新的Nashorn javascript引擎,它容許咱們在JVM上運行特定的javascript應用。Nashorn javascript引擎只是javax.script.ScriptEngine另外一個實現,並且規則也同樣,容許Java和JavaScript互相操做。這裏有個小例子:
1 2 3 4 5 |
|
輸出以下:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
4.5 Base64
對Base64的支持最終成了Java 8標準庫的一部分,很是簡單易用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
控制檯輸出的編碼和解碼的字符串
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
新的Base64API也支持URL和MINE的編碼解碼。
(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder()).
4.6 並行數組
Java 8新增長了不少方法支持並行的數組處理。最重要的大概是parallelSort()這個方法顯著地使排序在多核計算機上速度加快。下面的小例子演示了這個新的方法(parallelXXX)的行爲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
這一小段代碼使用parallelSetAll() t方法填充這個長度是2000的數組,而後使用parallelSort() 排序。這個程序輸出了排序前和排序後的10個數字來驗證數組真的已經被排序了。示例可能的輸出以下(請注意這些數字是隨機產生的)
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
4.7 併發
在新增Stream機制與lambda的基礎之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持彙集操做。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)(請查看咱們關於Java 併發的免費課程)。
新增的java.util.concurrent.locks.StampedLock類提供一直基於容量的鎖,這種鎖有三個模型來控制讀寫操做(它被認爲是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。
在java.util.concurrent.atomic包中還增長了下面這些類:
5. 新的工具
Java 8 提供了一些新的命令行工具,在這節裏咱們將會介紹它們中最有趣的部分。
5.1 Nashorn引擎:jjs
jjs是個基於Nashorn引擎的命令行工具。它接受一些JavaScript源代碼爲參數,而且執行這些源代碼。例如,咱們建立一個具備以下內容的func.js文件:
1 2 3 4 5 6 7 |
|
咱們能夠把這個文件做爲參數傳遞給jjs使得這個文件能夠在命令行中執行
1 |
|
輸出結果以下
2
更多的詳細信息請參考官方文檔。
5.2 類依賴分析工具:jdeps
Jdeps是一個功能強大的命令行工具,它能夠幫咱們顯示出包層級或者類層級java類文件的依賴關係。它接受class文件、目錄、jar文件做爲輸入,默認狀況下,jdeps會輸出到控制檯。
做爲例子,讓咱們看看如今很流行的Spring框架的庫的依賴關係報告。爲了讓報告短一些,咱們只分析一個jar: org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar 這個命令輸出內容不少,咱們只看其中的一部分,這些依賴關係根絕包來分組,若是依賴關係在classpath裏找不到,就會顯示not found.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
更多的詳細信息請參考官方文檔。
6. JVM的新特性
JVM內存永久區已經被metaspace替換(JEP 122)。JVM參數 -XX:PermSize 和 –XX:MaxPermSize被XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。
7. 結論
更多展望:Java 8經過發佈一些能夠增長程序員生產力的特性來推動這個偉大的平臺的進步。如今把生產環境遷移到Java 8還爲時尚早,可是在接下來的幾個月裏,它會被大衆慢慢的接受。毫無疑問,如今是時候讓你的代碼與Java 8兼容,而且在Java 8足夠安全穩定的時候遷移到Java 8。
做爲社區對Java 8的承認,最近Pivotal發佈了可在生產環境下支持Java 8的Spring Framework 4.0.3。