局部變量類型推斷是有爭議的熱點,但Java 10在JVM中的垃圾收集和容器識別上帶來了可喜的變化。
關(guān)于本系列
所以你認(rèn)為你了解Java編程? 事實是,大多數(shù)開發(fā)人員只是浮于Java平臺的表面上,僅僅為了完成工作而學(xué)習(xí)。在這個正在進(jìn)行的系列中,Java技術(shù)深入挖掘了Java平臺的核心功能,提出了一些技巧和訣竅,可以幫助你解決即使是最棘手的編程挑戰(zhàn)。
Java?開發(fā)人員已經(jīng)習(xí)慣了等待新的Java版本發(fā)布,但是新的、高頻率的發(fā)布節(jié)奏改變了這一情況。Java 9出現(xiàn)之后僅僅過去6個月,現(xiàn)在Java 10已經(jīng)在敲門了。再過6個月,我們將迎來Java 11。一些開發(fā)人員可能會發(fā)現(xiàn)這樣的快速發(fā)布是多余的,但是新的節(jié)奏標(biāo)志著一個長期需求的改變。
與它的版本號一樣,Java 10提供了10個新特性,本文提供了我認(rèn)為最重要的5個特性(您可以在Open JDK 10項目頁面上查看它們)。
1. Java的新版本節(jié)奏
從歷史上看,JDK發(fā)行的節(jié)奏是由大的新特性驅(qū)動的。作為最近的例子,Java 8以lambda和流的形式引入了函數(shù)式編程,而Java 9引入了模塊化Java系統(tǒng)。每個新版本都被熱切地期待著,但是次要的修復(fù)程序經(jīng)常束之高閣,等待更大的組件版本被最終確定。Java的進(jìn)化落后于其他語言。
新的高頻節(jié)奏將Java以更小的增量向前推進(jìn)。在發(fā)布日期準(zhǔn)備好的特性將被包括在內(nèi),而那些不能被安排在下一個版本中,就在6個月之后。在這個新周期下的第一個Java版本是Java 9,它于2017年10月發(fā)布。Java 10于2018年3月發(fā)布,Java 11將于2018年9月發(fā)布。
作為新節(jié)奏的一部分,甲骨文表示,它將只支持每個主要版本,直到下一個主要版本發(fā)布為止。 當(dāng)Java 11發(fā)布時,Oracle將停止支持Java 10。支持Java版本的開發(fā)人員必須每6個月遷移一次主要版本。 不希望或不需要頻繁遷移的開發(fā)人員可以使用LTS(長期支持)版本,該版本每三年更新一次。 目前的LTS版本Java 8將在今年秋季發(fā)布Java 11之前得到支持。
2. 局部變量類型推斷
局部變量類型推斷是Java 10中最顯著的特性。在進(jìn)入JDK 10之前,爭論非常激烈,該特性允許編譯器推斷局部變量的類型,而不是要求程序員明確指定它。
清單1 顯示了如何在Java 10之前定義一個String變量類型。
清單1.聲明并分配一個String類型的變量
清單2展示了在Java10中定義與String類型相同的變量
清單2.用局部變量類型推斷String類型的變量
正如你看到的,唯一的區(qū)別就是使用了var保留類型名稱。使用右邊的表達(dá)式,編譯器可以將變量名的類型推斷為String。
這看起來有點簡單,讓我們來看一個更加復(fù)雜的例子。如果一個變量分配給了調(diào)用方法的返回值是怎樣的?在這種情況下,編譯器可以根據(jù)方法的返回類型推斷變量的類型,如清單3所示。
清單3.從返回類型推斷String類型
使用局部變量類型
顧名思義,局部變量類型推斷功能僅適用于局部變量。 它不能用于定義實例或類變量,也不能用于方法參數(shù)或返回類型。 但是,您可以在類和增強型循環(huán)中使用var,可以從迭代器中推斷出類型,如清單4所示。
清單4.在循環(huán)中使用var
使用這種類型的最明顯的原因是為了減少代碼中的冗長。 看看清單5中的示例。
清單5.很長的類型名稱使得代碼很長
清單6中的類型聲明是垂直排列的,并且在構(gòu)造函數(shù)調(diào)用的右側(cè)每個申明中都會提到一次類型。 想象一下使用這種類型在一些Java框架中常見的長類名的好處。
局部變量類型的問題
1. var掩蓋了類型
你已經(jīng)看到了var如何提高代碼的可讀性,但是從另一方面來看,它也可以掩蓋它。 看看清單7中的示例。
清單7.返回類型不清楚
在清單7中,我們必須猜測返回類型。 讓讀者猜測發(fā)生了什么的代碼是難以維護(hù)的。
2. var不能與lambda一起使用
與lambda表達(dá)式一起使用時,類型推斷效果不佳,主要原因是編譯器缺少類型信息。 清單8中的lambda表達(dá)式不會被編譯。
清單8.類型信息不足
在清單8中,編譯器的右邊表達(dá)式中沒有足夠的類型信息來推斷變量類型。 Lambda語句必須始終聲明一個顯式類型。
3. var不會與菱形操作符混在一起
與菱形操作符一起使用時,類型推斷也不能很好地工作。 看看清單9中的例子。
清單9.使用帶有var的菱形運算符
親自嘗試一下
想要親自嘗試本地變量類型推斷,您需要下載JDK 10和一個支持它的IDE。 IntelliJ的EAP(Early Access Program)版本具有此支持。 一旦你下載并安裝了它,你可以從本文附帶的GitHub存儲庫中檢出代碼開始。 你會在那里找到局部變量類型推斷的例子。
在代碼清單9中,books的ArrayList的參數(shù)類型是什么呢?你可能明白你是希望ArrayList存儲一個書的列表,但是編譯器不能推斷出來。反之,編譯器會做的唯一它能做的事情,就是推斷出來這是一個參數(shù)是Object類型ArrayList:ArrayList()。
另外一種方法就是在右端表達(dá)式中的菱形運算符中定義具體類型。然后你可以讓編譯器從而推斷出來變量的類型,就像在代碼清單10中寫的一樣。或者使用另外一種方式,即你必須明確地以傳統(tǒng)方式聲明變量:List books。事實上,你可能更喜歡這種方式,因為它能讓你定義一個抽象類型,并對List接口編程:
清單10. 定義出具體類型
3、增加、刪除和棄用
刪除
Java 10刪除了很多工具:
命令行工具javah,可以使用javac -h代替。
命令行選項-X:prof,盡管可以使用jmap工具來訪問分析信息。
政策工具。
一些從Java1.2開始標(biāo)記的為已棄用的API也被永久刪除了。包括java.lang.SecurityManager.inCheck字段和以下方法:
java.lang.SecurityManager.classDepth(java.lang.String)
java.lang.SecurityManager.classLoaderDepth()
java.lang.SecurityManager.currentClassLoader()
java.lang.SecurityManager.currentLoadedClass()
java.lang.SecurityManager.getInCheck()
java.lang.SecurityManager.inClass(java.lang.String)
java.lang.SecurityManager.inClassLoader()
java.lang.Runtime.getLocalizedInputStream(java.io.InputStream)
java.lang.Runtime.getLocalizedOutputStream(java.io.OutputStream)
棄用
JDK 10也棄用了一些API。 java.security.acl包已標(biāo)記為已棄用,也包括java.security包中包含各種相關(guān)的類(Certificate,Identity,IdentityScope,Singer,auth.Policy)。
此外,javax.management.remote.rmi.RMIConnectorServer類中的CREDENTIAL_TYPES被標(biāo)記為不建議使用。 java.io.FileInputStream和java.io.FileOutputStream中的finalize()方法已被標(biāo)記為已棄用。所以在java.util.zip.Deflater / Inflater / ZipFileclasses中的finalize()方法也被棄用。
添加和包含
作為Oracle JDK和Open JDK正在進(jìn)行對接的一部分,Open JDK現(xiàn)在包含Oracle JDK中可用的一部分根證書頒發(fā)機構(gòu)。這些包括Java Flight Recorder和Java Mission Control。此外,JDK 10在java.text,java.time和java.util包的適當(dāng)位置中增加了對BCP 47語言標(biāo)記的Unicode擴展的增強支持。另一項新功能允許在不執(zhí)行全局VM安全點的情況下執(zhí)行線程回調(diào)。這使停止單個線程既可行又便宜,而不是要求你停止所有線程或不需要任何線程。
4.提高容器意識
如果你部署到像Docker這樣的容器,那么這個功能特別適合你。 現(xiàn)在JVM意識到它正在容器中運行,并查詢?nèi)萜髦锌捎锰幚砥鞯臄?shù)量,而不是查詢主機操作系統(tǒng)。 也可以從外部附加到在容器中運行的Java進(jìn)程,這使監(jiān)視JVM進(jìn)程變得更加容易。
以前,JVM不知道它的容器,并會向主機操作系統(tǒng)詢問活動CPU的數(shù)量。 在某些情況下,這會導(dǎo)致JVM過度報告資源,導(dǎo)致多個容器在同一操作系統(tǒng)上運行時出現(xiàn)問題。 在Java 10中,您可以將容器配置為使用主機操作系統(tǒng)的CPU的子集,并且JVM將能夠確定正在使用的CPU數(shù)量。 您還可以使用-XX:ActiveProcessorCount標(biāo)志明確指明能夠看到的容器化JVM處理器數(shù)量。
5.應(yīng)用程序類數(shù)據(jù)共享
此特性的用途是提高運行間和多個運行相同代碼的JVM啟動時間,同時減少內(nèi)存占用量。 這通過在JVM之間共享關(guān)于類的元數(shù)據(jù)來實現(xiàn)。 JVM的第一次運行收集并歸檔有關(guān)它所加載的類的數(shù)據(jù)。 然后它將數(shù)據(jù)文件提供給其他JVM以及該JVM的后續(xù)運行,從而節(jié)省JVM初始化過程中的時間和資源。 類數(shù)據(jù)共享實際上已經(jīng)有一段時間了,但僅限于系統(tǒng)類。 現(xiàn)在這個功能已經(jīng)擴展到包含所有的應(yīng)用程序類。
結(jié)束語
Java10中頭號特性是把Var作為了新的類型名,它可以讓代碼更加簡潔和清晰。但是,如果使用不謹(jǐn)慎也會掩蓋住原來的含義和意圖。當(dāng)不明確含義的時候,IDE或許可以幫助你辨別類型,但是在一個IDE中無法讀取所有類型的代碼。我們經(jīng)常通過GitHub倉庫、調(diào)試器或者代碼審查工具在線閱讀代碼。開發(fā)者使用這個新的特性時,務(wù)必注意為了將來的讀者和維護(hù)人員提高代碼可讀性。
Java的新版本如此高頻率發(fā)布是一個值得歡迎的改變。在發(fā)布日期,已經(jīng)準(zhǔn)備好的特性必須發(fā)布,那些延遲的特性將在短暫的調(diào)整之后再下個版本發(fā)布。新的循環(huán)將加快java的發(fā)展進(jìn)程,那些已經(jīng)開發(fā)完成并且已經(jīng)列出來的特性,開發(fā)者不需要等好多年。
從一個主要版本到下一個主要版本的發(fā)布的支持時間越來越短,這帶來一些合理的擔(dān)憂,但是LTS應(yīng)該可以有效的緩解該問題。發(fā)布疲勞是另一個風(fēng)險,因為開發(fā)者對頻繁的版本更新感到厭煩??偟膩碚f,我認(rèn)為這是一個積極的行為,在未來很長的一段時間里,它有助于保證java的活躍度和維持java的發(fā)展。