你想了解的JDK 10版本更新都在這里
- 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎(chǔ)教程是自己在結(jié)合各方面的知識之后,對 Java 基礎(chǔ)的一個總回顧,旨在 「幫助新朋友快速高質(zhì)量的學習」。
 - 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關(guān)注 + 留言 + 分享」,文末有完整的獲取鏈接,您的支持是我前進的最大的動力!
 
特性總覽
以下是 Java 10 中的引入的部分新特性。關(guān)于 Java 10 新特性更詳細的介紹可參考這里。
- 基于時間的發(fā)行版本控制(JEP 322)
 - 局部變量類型推斷(JEP 286)
 - 試驗性 JIT 編譯器(JEP 317)
 - 應(yīng)用程序類數(shù)據(jù)共享(JEP 310)
 - 用于 G1 的并行 Full GC(JEP 307)
 - 清理垃圾收集器接口(JEP 304)
 - 其他 Unicode 語言標簽擴展(JEP 314)
 - 根證書(JEP 319)
 - 線程本地握手(JEP 312)
 - 備用存儲設(shè)備上的堆分配(JEP 316)
 - 刪除本機頭生成工具——javah(JEP 313)
 - 將JDK森林合并到單個存儲庫中(JEP 296)
 - API 變更
 
一. 基于時間的發(fā)行版本控制(JEP 322)
發(fā)行版本號的前世今生
自 Java 江山易主,JDK 發(fā)行版本的字符串命名方式一直是一個耐人尋味的話題。
單就下載 JDK 時,所看到的簡短版本字符串形式來說,在 7u40 版本之前,u 之后的數(shù)字,代表了 JDK 發(fā)布以來的第幾個修正版本,然而 Oracle 改變規(guī)則,為了彰顯出安全之類的重大 修補(Cirtical Patch Updates)版本,采用 奇數(shù) 命名,而 Bug 修復、API 修改之類的 維護版本,則采用 偶數(shù)。(另有版本號 $MAJOR.$MINOR.$SECURITY 的格式來區(qū)分 Bug 修正和 API 修改)
為此,之前既有的 JDK 6/7 發(fā)布版本,還被重新命名。(下方演示)
- Actual Hypothetical
 - Release Type long short
 - ------------ ------------------------
 - Security 2013/06 1.7.0_25-b15 7u25
 - Minor 2013/09 1.7.0_40-b43 7u40
 - Security 2013/10 1.7.0_45-b18 7u45
 - Security 2014/01 1.7.0_51-b13 7u51
 - Minor 2014/05 1.7.0_60-b19 7u60
 
就結(jié)論而言,重新命名之后,從 7u9、6u37 開始,就可以從奇偶數(shù)來判別是否為重大修補版本;至于 7u40 之后的版本,重大修補版本 是基于 5 的倍數(shù),遇偶數(shù)加一,而 維護版本則會是 20 的倍數(shù)。如此(不直觀)的命名方式,被規(guī)范在了 JEP223 中。(隨著 JDK 9 一起發(fā)布的)
然而,自 JDK 8 發(fā)布之后,Oracle 的 Java 架構(gòu)師 Mark Reinhold 就希望,未來 Java 發(fā)布可以基于時間,以半年為周期,持續(xù)發(fā)布新版本,讓一些有用的小特性,也能被開發(fā)者使用,因此,JEP 223 的規(guī)范就不再適用了,而在 JDK 9 發(fā)布后,他們針對新版本曾經(jīng)提出過基于$YEAR.$MONTH 格式,然而收到了社區(qū)極大的反對,為此,還提出了三個替代方案,收集各方的意見。(https://goo.gl/7CA8B3)
那么,Java 下一個特性版本是 8.3?1803?還是 10?調(diào)查結(jié)果顯示,社群大多數(shù)都支持10,Stephen Colebourne 也發(fā)出請求(https://goo.gl/i5J44T),并表示 Java 不像 Ubuntu 這類操作系統(tǒng),基于 $YEAR.$MONTH 并不合適。
最終,Oracle 采用了 $FEATURE.$INTERIM.$UPDATE.$PATCH 這樣的方案,并規(guī)定在了 JEP 322 中。
JEP 322 新模式解讀
通過采用基于時間的發(fā)行周期,Oracle 更改了 Java SE 平臺和 JDK 的版本字符串方案以及相關(guān)的版本信息,以適用于當前和將來的基于時間的發(fā)行模型。
版本號的新模式是:
$FEATURE.$INTERIM.$UPDATE.$PATCH
- $ FEATURE:計數(shù)器將每 6 個月遞增一次,并基于功能發(fā)布版本,例如:JDK 10,JDK 11。
 - $ INTERIM:對于包含兼容錯誤修復和增強功能但沒有不兼容更改的非功能版本,計數(shù)器將增加。通常,這將是零,因為六個月內(nèi)不會有任何臨時發(fā)布。這保留了對發(fā)布模型的將來修訂。
 - $ UPDATE:計數(shù)器將增加,用于解決安全問題,回歸和較新功能中的錯誤的兼容更新版本。此功能會在功能發(fā)布后一個月更新,此后每三個月更新一次。2018 年 4 月版本是 JDK 10.0.1,7 月版本是 JDK 10.0.2,依此類推
 - $ PATCH:計數(shù)器將增加以用于緊急釋放以解決嚴重問題。
 
并添加了新的 API 以通過編程的方式獲取這些計數(shù)器:
- Version version = Runtime.version();
 - version.feature();
 - version.interim();
 - version.update();
 - version.patch();
 
現(xiàn)在,讓我們來看一下返回版本信息的 Java 啟動器:
- $ java -version
 - java version "10" 2018-03-20
 - Java(TM) SE Runtime Environment 18.3 (build 10+46)
 - Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)
 
版本號格式為 10,因為沒有其他計數(shù)器為零。發(fā)布日期已添加??梢詫?18.3 理解為 Year 2018&3rd Month,版本 10 + 46 是版本 10 的 46 版本。對于 JDK 10.0.1 的假設(shè)版本93,版本將為 10.0.1 + 93。
因此,對于 Java 9 來說,目前可以看到的版本,會是 9.0.4 這樣的格式,至于 2018 年 3 月發(fā)布的特性版本為 10,到了 9 月提供的新版本,就是 11,JDK 11 預(yù)計會是長期支援版本,所以,在版本字串上,也會特別顯示 LTS(long-term support)。
二. 局部變量類型推斷(JEP 286)
概述
JDK 10 中最明顯的增強功能之一是使用初始化程序?qū)植孔兞窟M行類型推斷。
在 Java 9 之前,我們必須明確寫出局部變量的類型,并確保它與用于初始化它的初始化程序兼容:
- String message = "Good bye, Java 9";
 
在 Java 10 中,這是我們可以聲明局部變量的方式:
- @Test
 - public void whenVarInitWithString_thenGetStringTypeVar() {
 - var message = "Hello, Java 10";
 - assertTrue(message instanceof String);
 - }
 
我們沒有提供 message 的具體類型,相反,我們把 message 標記為了 var,編譯器將從右側(cè)的初始化程序的類型推斷出 message 的類型。(上面的例子中 message 為 String 類型)
請注意,此功能僅適用于帶有初始化程序的局部變量。它不能用于成員變量、方法參數(shù)、返回類型等——初始化程序是必須的,否則,編譯器無法推斷出其類型。
這個功能有助于我們減少樣板式的代碼,例如:
- Map<Integer, String> map = new HashMap<>();
 
現(xiàn)在可以改寫為:
- var idToNameMap = new HashMap<Integer, String>();
 
這也有助于我們把重點放在變量名,而不是變量類型上。
要注意的另一件事是 var 不是關(guān)鍵字——這確保了使用 var 作為函數(shù)或變量名的程序的向后兼容性。var 是一個保留類型名,就像 int 一樣。
最后,使用 var 不會增加運行時的開銷,也不會使 Java 稱為動態(tài)類型的語言。變量的類型仍然是在編譯時進行判斷,以后也無法更改。
非法使用 var 的情況解析
1、如果沒有初始化程序,var 將無法工作:
- var n; // error: cannot use 'var' on variable without initializer
 
2、如果將其初始化為 null,也不會起作用:
- var emptyList = null; // error: variable initializer is 'null'
 
3、不適用于非局部變量:
- public var word = "hello"; // error: 'var' is not allowed here
 
4、Lambda 表達式需要顯式的類型,因此無法使用 var:
- var p = (String s) -> s.length() > 10; // error: lambda expression needs an explicit target-type
 
5、數(shù)組初始化程序也不支持:
- var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type
 
使用 var 的準則
在某些情況下,我們可以合法使用 var,但這樣做并不是一個好主意。
例如,在代碼的可讀性降低的情況下:
- var result = obj.prcoess();
 
在這里,盡管可以合法使用 var,但很難理解 process() 返回的類型,從而讓代碼的可讀性降低。
java.net(OpenJDK 官網(wǎng))上專門有一篇文章介紹了 Java 中的局部變量類型推斷的書寫準則,該文章討論了在使用此功能時應(yīng)該注意的姿勢和如何使用的一些良好建議。
另外,最好避免使用 var 的另一種情況是在流水線較長的流中:
- var x = emp.getProjects.stream()
 - .findFirst()
 - .map(String::length)
 - .orElse(0);
 
另外,將 var 與不可引用類型一起使用可能會導致意外錯誤。
比如,如果我們將 var 與匿名類實例一起使用:
- @Test
 - public void whenVarInitWithAnonymous_thenGetAnonymousType() {
 - var obj = new Object() {};
 - assertFalse(obj.getClass().equals(Object.class));
 - }
 
現(xiàn)在,如果我們嘗試將另一個 Object 分配給 obj,則會出現(xiàn)編譯錯誤:
- obj = new Object(); // error: Object cannot be converted to <anonymous Object>
 
這是因為 obj 的推斷類型不是 Object。
三. 試驗性 JIT 編譯器(JEP 317)
Graal 是用Java編寫的,與 HotSpot JVM 集成的動態(tài)編譯器。它專注于高性能和可擴展性。它也是 JDK 9 中引入的實驗性 Ahead-of-Time(AOT)編譯器的基礎(chǔ)。
JDK 10 使 Graal 編譯器可以用作 Linux / x64 平臺上的實驗性 JIT 編譯器。
要將 Graal 用作 JIT 編譯器,請在 Java 命令行上使用以下選項:
- -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
 
請注意,這是一個實驗性功能,我們不一定會獲得比現(xiàn)有JIT編譯器更好的性能。
想要了解更多內(nèi)容的童鞋請參考 Chris Seaton 的演講:https://chrisseaton.com/truffleruby/jokerconf17/
(ps:長文 + 特別底層警告..)
四. 應(yīng)用程序類數(shù)據(jù)共享(JEP 310)
JDK 5 中引入的類數(shù)據(jù)共享允許將一組類預(yù)處理成共享的歸檔文件,然后在運行時對其進行內(nèi)存映射,以減少啟動時間,這還可以在多個 JVM 共享相同的歸檔文件時減少動態(tài)內(nèi)存占用。
CDS 只允許引導類裝入器,將該特性限制為系統(tǒng)類。應(yīng)用程序 CDS (AppCDS)擴展了 CDS 以允許內(nèi)置的系統(tǒng)類裝入器。內(nèi)置的平臺類裝入器和用于裝入歸檔類的自定義類裝入器。這使得對應(yīng)用程序類使用該特性成為可能。
我們可以使用以下步驟來使用這個功能:
1、獲取要存檔的類列表
以下命令會將HelloWorld 應(yīng)用程序加載的類轉(zhuǎn)儲到hello.lst中:
- $ java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=hello.lst \
 - -cp hello.jar HelloWorld
 
2、創(chuàng)建 AppCDS 存檔
以下命令使用hello.lst 作為輸入創(chuàng)建hello.js a :
- $ java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=hello.lst \
 - -XX:SharedArchiveFile=hello.jsa -cp hello.jar
 
3、使用 AppCDS 存檔
以下命令以hello.jsa 作為輸入啟動HelloWorld 應(yīng)用程序:
- $ java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
 - -cp hello.jar HelloWorld
 
AppCDS 是用于 JDK 8 和 JDK 9 的 Oracle JDK 中的一項商業(yè)功能?,F(xiàn)在它是開源的,并且可以公開使用。
五. 用于 G1 的并行 Full GC(JEP 307)
G1 垃圾收集器是自 JDK 9 以來的默認垃圾收集器。但是,G1 的 Full GC 使用了單線程的 mark-sweep-compact 算法。
它已 更改為 Java 10 中 的并行mark-sweep-compact算法 ,有效地減少了 Full GC 期間的停滯時間。
六. 清理垃圾收集器接口(JEP 304)
這個 JEP 是未來的變化。通過引入公共垃圾收集器接口,它改善了不同垃圾收集器的代碼隔離。
次更改為內(nèi)部 GC 代碼提供了更好的模塊化。將來將有助于在不更改現(xiàn)有代碼庫的情況下添加新 GC,還有助于刪除或保留以前的 GC。
官方的動機解釋: (傳送門)
當前,每個垃圾收集器實現(xiàn)都由其 src/hotspot/share/gc/$NAME 目錄內(nèi)的源文件組成,例如 G1 在中 src/hotspot/share/gc/g1,CMS 在 src/hotspot/share/gc/cms 等中。但是,在 HotSpot 中散布著一些零散的信息。例如,大多數(shù) GC 需要某些障礙,這些障礙需要在運行時,解釋器 C1 和 C2 中實現(xiàn)。這些障礙并不包含在 GC 的具體目錄,但在共享解釋器,而不是實施,C1 和 C2 的源代碼(通常由長守衛(wèi)if- else-chains)。同樣的問題也適用于診斷代碼,例如 MemoryMXBeans。此源代碼布局有幾個缺點:
對于GC開發(fā)人員,實施新的垃圾收集器需要有關(guān)所有這些地方的知識,以及如何擴展它們以滿足其特定需求的知識。
對于不是 GC 開發(fā)人員的 HotSpot 開發(fā)人員,在哪里為給定 GC 找到特定的代碼段會造成混亂。
在構(gòu)建時很難排除特定的垃圾收集器。該#define INCLUD E_ALL_GCS長期以來建立與唯一內(nèi)置串行收集JVM的一種方式,但這種機制變得過于呆板。
較干凈的 GC 接口將使實現(xiàn)新的收集器更加容易,使代碼更加清潔,并且在構(gòu)建時排除一個或多個收集器也更加容易。添加一個新的垃圾收集器應(yīng)該是實現(xiàn)一組有據(jù)可查的接口,而不是弄清 HotSpot 中所有需要更改的地方。
七. 其他Unicode語言標簽擴展(JEP 314)
此功能增強了 java.util.Locale 和相關(guān) API,以實現(xiàn) BCP 47 語言標簽的其他 Unicode 擴展。從 Java SE 9 開始,受支持的 BCP 47 U 語言擴展名是 "ca" 和 "nu"。該 JEP 將增加對以下附加擴展的支持:
- cu(貨幣類型)
 - fw(一周的第一天)
 - rg(區(qū)域覆蓋)
 - tz(時區(qū))
 
為了支持這些附加擴展,對以下各種 API 進行了更改以提供基于 U 或附加擴展的信息:
- java.text.DateFormat::get*Instance
 - java.text.DateFormatSymbols::getInstance
 - java.text.DecimalFormatSymbols::getInstance
 - java.text.NumberFormat::get*Instance
 - java.time.format.DateTimeFormatter::localizedBy
 - java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
 - java.time.format.DecimalStyle::of
 - java.time.temporal.WeekFields::of
 - java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
 - java.util.Currency::getInstance
 - java.util.Locale::getDisplayName
 - java.util.spi.LocaleNameProvider
 
八. 根證書(JEP 319)
cacerts 密鑰庫(迄今為止到目前為止是空的)旨在包含一組根證書,這些根證書可用于建立對各種安全協(xié)議所使用的證書鏈的信任。
結(jié)果,在 OpenJDK 構(gòu)建中,諸如 TLS 之類的關(guān)鍵安全組件默認情況下不起作用。
借助 Java 10,Oracle 將 Oracle Java SE Root CA 程序中 的根證書開源了 ,以使 OpenJDK 構(gòu)建對開發(fā)人員更具吸引力,并減少了這些構(gòu)建與 Oracle JDK 構(gòu)建之間的差異。
九. 線程本地握手(JEP 312)
這是用于提高 JVM 性能的內(nèi)部特性。
握手操作是在線程處于安全點狀態(tài)時為每個 JavaThread 執(zhí)行的回調(diào)?;卣{(diào)由線程本身或 VM 線程執(zhí)行,同時保持線程處于阻塞狀態(tài)。
這個特性提供了一種無需執(zhí)行全局 VM 安全點即可在線程上執(zhí)行回調(diào)的方法。使停止單個線程,而不是停止所有線程或不停止線程成為可能,而且代價低廉。
十. 備用存儲設(shè)備上的堆分配(JEP 316)
應(yīng)用程序的內(nèi)存消耗越來越大,本地云應(yīng)用程序、內(nèi)存中的數(shù)據(jù)庫、流應(yīng)用程序都在增加。為了滿足這些服務(wù),有各種可用的內(nèi)存架構(gòu)。這個特性增強了 HotSpot VM 在用戶指定的備用內(nèi)存設(shè)備(比如NV-DIMM)上分配 Java 對象堆的能力。
這個 JEP 的目標是具有與 DRAM 相同語義(包括原子操作的語義)的可選內(nèi)存設(shè)備,因此,可以在不更改現(xiàn)有應(yīng)用程序代碼的情況下,將其用于對象堆,而不是用于 DRAM。
十一. 刪除本機頭生成工具—javah (JEP 313)
這是一個從 JDK 中刪除 javah 工具的常規(guī)更改。工具功能是作為 JDK 8 的一部分在 javac中添加的,它提供了在編譯時編寫使 javah 無用的本機頭文件的能力。
十二. 將JDK森林合并到單個存儲庫中(JEP 296)
多年來,有各種各樣的 Mercurial 存儲庫用于 JDK 代碼基。不同的存儲庫確實提供了一些優(yōu)勢,但它們也有各種操作上的缺點。作為這個變化的一部分,JDK 森林的許多存儲庫被合并到一個存儲庫中,以簡化和簡化開發(fā)。
十三. API 變更
Java 10 添加和刪除了 API。Java 9 引入了增強的棄用,其中某些 API 被標記為將在未來的版本中刪除。
于是這些 API 被刪除了:你可以在 這里 找到被刪除的 API。
新增API: Java 10 中新增 73 個API。您可以在 這里 找到添加的 API 并進行比較。
讓我們來看看對我們直接有用的部分。
不可修改集合的改進
Java 10 中有一些與不可修改集合相關(guān)的更改
copyOf()
java.util.List、java.util.Map 和 java.util.Set 都有了一個新的靜態(tài)方法 copyOf(Collection)。
它返回給定 Collection 的不可修改的副本:
- jshell> var originList = new ArrayList<String>();
 - originList ==> []
 - jshell> originList.add("歡迎關(guān)注公眾號:");
 - $2 ==> true
 - jshell> originList.add("我沒有三顆心臟");
 - $3 ==> true
 - jshell> var copyList = List.copyOf(originList)
 - copyList ==> [歡迎關(guān)注公眾號:, 我沒有三顆心臟]
 - jshell> originList.add("獲取更多精彩內(nèi)容")
 - $5 ==> true
 - jshell> System.out.println(copyList)
 - [歡迎關(guān)注公眾號:, 我沒有三顆心臟]
 - jshell> copyList.add("獲取更多精彩內(nèi)容")
 - | 異常錯誤 java.lang.UnsupportedOperationException
 - | at ImmutableCollections.uoe (ImmutableCollections.java:73)
 - | at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:77)
 - | at (#7:1)
 - jshell>
 
toUnmodifiable()
java.util.Collectors 獲得其他方法來將 Stream 收集到不可修改的 List、Map 或 Set 中:
- @Test(expected = UnsupportedOperationException.class)
 - public void whenModifyToUnmodifiableList_thenThrowsException() {
 - List<Integer> evenList = someIntList.stream()
 - .filter(i -> i % 2 == 0)
 - .collect(Collectors.toUnmodifiableList());
 - evenList.add(4);
 - }
 
任何嘗試修改此類集合的嘗試都會導致 java.lang.UnsupportedOperationException 運行時異常。
Optinal 新增方法 orElseThrow()
java.util.Optional,java.util.OptionalDouble,java.util.OptionalInt 和 java.util.OptionalLong 都有一個新方法 orElseThrow(),它不接受任何參數(shù),如果不存在任何值,則拋出 NoSuchElementException:
- @Test
 - public void whenListContainsInteger_OrElseThrowReturnsInteger() {
 - Integer firstEven = someIntList.stream()
 - .filter(i -> i % 2 == 0)
 - .findFirst()
 - .orElseThrow();
 - is(firstEven).equals(Integer.valueOf(2));
 - }
 
它與現(xiàn)有的 get() 方法同義,并且現(xiàn)在是它的首選替代方法。
參考資料
- OpenJDK 官方說明 - http://openjdk.java.net/projects/jdk/10/
 - Java 10 Features | JournalDev - https://www.journaldev.com/20395/java-10-features
 - 想跳舞的 Java | 林信良 - https://www.ithome.com.tw/voice/122249
 - Java 10 Performance Improvements | Baeldung - https://www.baeldung.com/java-10-performance-improvements
 
本文轉(zhuǎn)載自微信公眾號「 我沒有三顆心臟」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 我沒有三顆心臟公眾號。
















 
 
 






 
 
 
 