jdk9新特徵系列

(一):jdk和jre的改變

JDK和JRE已經在Java SE 9中進行了模塊化處理。在Java SE 9之前,JDK構建系統用於生成兩種類型的運行時映像 ——Java運行時環境(JRE)和Java開發工具包(JDK)。 JRE是Java SE平臺的完整實現,JDK包含了JRE和開發工具和類庫。 可下圖顯示了Java SE 9之前的JDK安裝中的主目錄。JDK_HOME是安裝JDK的目錄。 如果你只安裝了JRE,那麼你只有在jre目錄下的目錄。

在 Java SE 9之前,JDK中:
bin目錄用於包含命令行開發和調試工具,如javac,jar和javadoc。 它還用於包含Java命令來啓動Java應用程序。
include目錄包含在編譯本地代碼時使用的C/C++頭文件。
lib目錄包含JDK工具的幾個JAR和其他類型的文件。 它有一個tools.jar文件,其中包含javac編譯器的Java類。
jre\bin目錄包含基本命令,如java命令。 在Windows平臺上,它包含系統的運行時動態鏈接庫(DLL)。
jre\lib目錄包含用戶可編輯的配置文件,如.properties和.policy文件。
jre\lib\approved目錄包含允許使用標準覆蓋機制的JAR。 這允許在Java社區進程之外創建的實施標準或獨立技術的類和接口的更高版本被併入Java平臺。 這些JAR被添加到JVM的引導類路徑中,從而覆蓋了Java運行時中存在的這些類和接口的任何定義。
jre\lib\ext目錄包含允許擴展機制的JAR。 該機制通過擴展類加載器(該類加載器)加載了該目錄中的所有JAR,該引導類加載器是系統類加載器的子進程,它加載所有應用程序類。 通過將JAR放在此目錄中,可以擴展Java SE平臺。 這些JAR的內容對於在此運行時映像上編譯或運行的所有應用程序都可見。
jre\lib目錄包含幾個JAR。 rt.jar文件包含運行時的Java類和資源文件。 許多工具依賴於rt.jar文件的位置。
jre\lib目錄包含用於非Windows平臺的動態鏈接本地庫。
jre\lib目錄包含幾個其他子目錄,其中包含運行時文件,如字體和圖像。

Java SE 9調整了JDK的目錄層次結構,並刪除了JDK和JRE之間的區別。 下圖顯示了Java SE 9中JDK安裝的目錄。JDK 9中的JRE安裝不包含include和jmods目錄。

在Java SE 9 的JDK中:
沒有名爲jre的子目錄。
bin目錄包含所有命令。 在Windows平臺上,它繼續包含系統的運行時動態鏈接庫。
conf目錄包含用戶可編輯的配置文件,例如以前位於jre\lib目錄中的.properties和.policy文件。
include目錄包含要在以前編譯本地代碼時使用的C/C++頭文件。 它只存在於JDK中。
jmods目錄包含JMOD格式的平臺模塊。 創建自定義運行時映像時需要它。 它只存在於JDK中。
legal 目錄包含法律聲明。
lib目錄包含非Windows平臺上的動態鏈接本地庫。 其子目錄和文件不應由開發人員直接編輯或使用。

(二):訪問資源

資源是應用程序使用的數據,例如圖像,音頻,視頻,文本文件等。Java提供了一種通過在類路徑上定位資源來訪問資源的位置無關的方式。 需要與在JAR中打包類文件相同的方式打包資源,並將JAR添加到類路徑。 通常,類文件和資源打包在同一個JAR中。 訪問資源是每個Java開發人員執行的重要任務。

1. 在JDK 9之前訪問資源

在Java代碼中,資源由資源名稱標識,資源名稱是由斜線(/)分隔的一串字符串。 對於存儲在JAR中的資源,資源名稱僅僅是存儲在JAR中的文件的路徑。 例如,在JDK 9之前,存儲在rt.jar中的java.lang包中的Object.class文件是一個資源,其資源名稱是java/lang/Object.class。
在JDK 9之前,可以使用以下兩個類中的方法來訪問資源:
java.lang.Class
java.lang.ClassLoader
資源由ClassLoader定位。 一個Class代理中的資源尋找方法到它的ClassLoader。 因此,一旦瞭解ClassLoader使用的資源加載過程,將不會在使用Class類的方法時遇到問題。 在兩個類中有兩種不同的命名實例方法:
URL getResource(String name)
InputStream getResourceAsStream(String name)
兩種方法都會以相同的方式找到資源。 它們的差異僅在於返回類型。 第一個方法返回一個URL,而第二個方法返回一個InputStream。 第二種方法相當於調用第一種方法,然後在返回的URL對象上調用openStream()。
ClassLoader類包含三個額外的查找資源的靜態方法:
static URL getSystemResource(String name)
static InputStream getSystemResourceAsStream(String name)
static Enumeration<URL> getSystemResources(String name)
這些方法使用系統類加載器(也稱爲應用程序類加載器)來查找資源。 第一種方法返回找到的第一個資源的URL。 第二種方法返回找到的第一個資源的InputStream。 第三種方法返回使用指定的資源名稱找到的所有資源的URL枚舉。
要找到資源,有兩種類型的方法可以從——getSystemResource*和getResource*中進行選擇。 在討論哪種方法是最好的之前,重要的是要了解有兩種類型的資源:
系統資源
非系統資源
你必須瞭解他們之間的區別,以瞭解資源查找機制。系統資源是在bootstrap類路徑,擴展目錄中的JAR和應用程序類路徑中找到的資源。非系統資源可以存儲在除路徑之外的位置,例如在特定目錄,網絡上或數據庫中。 getSystemResource()方法使用應用程序類加載程序找到一個資源,委託給它的父類,它是擴展類加載器,後者又委託給它的父類(引導類加載器)。如果你的應用程序是獨立的應用程序,並且它只使用三個內置的JDK類加載器,那麼你將很好的使用名爲getSystemResource *的靜態方法。它將在類路徑中找到所有資源,包括運行時映像中的資源,如rt.jar文件。如果你的應用程序是在瀏覽器中運行的小程序,或在應用程序服務器和Web服務器中運行的企業應用程序,則應使用名爲getResource*的實例方法,它可以使用特定的類加載器來查找資源。如果在Class對象上調用getResource*方法,則會使用當前類加載器(加載Class對象的類加載器)來查找資源。
傳遞給ClassLoader類中所有方法的資源名稱都是絕對的,它們不以斜線(/)開頭。 例如,當調用ClassLoader的getSystemResource()方法時,將使用java/lang/Object.class作爲資源名稱。
Class類中的資源查找方法可以指定絕對和相對資源名稱。絕對資源名稱以斜線開頭,而相對資源名稱不用。 當使用絕對名稱時,Class類中的方法會刪除前導斜線並委派給加載Class對象的類加載器來查找資源。 以下調用
Test.class.getResource("/resources/test.config");
會被轉換成
Test.class.getClassLoader().getResource("resources/test.config");
當使用相對名稱時,Class類中的方法預先添加了包名稱,在使用斜線後跟斜線替換包名中的點,然後再委託加載Class對象的類加載器來查找資源。 假設測試類在com.jdojo.test包中,以下調用:
Test.class.getResource("resources/test.config");
會被轉換成
Test.class.getClassLoader().getResource("com/jdojo/test/resources/test.config");

2. 在JDK 9 中訪問資源

在JDK 9之前,可以從類路徑上的任何JAR訪問資源。 在JDK 9中,類和資源封裝在模塊中。 在第一次嘗試中,JDK 9設計人員強制執行模塊封裝規則,模塊中的資源必須對該模塊是私有的,因此它們只能在該模塊內的代碼中訪問。 雖然這個規則在理論上看起來很好,但是對於跨模塊共享資源的框架和加載的類文件作爲來自其他模塊的資源,就會帶來問題。 爲了有限地訪問模塊中的資源,做了一些妥協,但是仍然強制執行模塊的封裝。 JDK 9包含三類資源查找方法:
java.lang.Class
java.lang.ClassLoader
java.lang.Module
Class和ClassLoader類沒新增任何新的方法。 Module類包含一個getResourceAsStream(String name)方法,如果找到該資源,返回一個InputStream;否則返回null。

(三):模塊化

模塊化特性是Java 9 最大的一個特性,Java 9起初的代號就叫Jigsaw,後來被更改爲Modularity,Modularity提供了類似於OSGI框架的功能,模塊之間存在相互的依賴關係,可以導出一個公共的API,並且隱藏實現的細節,Java提供該功能的主要的動機在於,減少內存的開銷,我們大家都知道,在JVM啓動的時候,至少會有30~60MB的內存加載,主要原因是JVM需要加載rt.jar,不管其中的類是否被classloader加載,第一步整個jar都會被JVM加載到內存當中去,模塊化可以根據模塊的需要加載程序運行需要的class,那麼JVM是如何知道需要加載那些class的呢?這就是在Java 9 中引入的一個新的文件module.java我們大致來看一下一個例子(module-info.java)

關於更多Java 9 模塊編程的內容請參考一本書:《Java 9 Modularity》 裏面講的比較詳細,介紹了當前Java對jar之間以來的管理是多麼的混亂,引入modularity之後的改變會是很明顯的差別。

Java 平臺級模塊系統

Java 9的定義功能是一套全新的模塊系統。當代碼庫越來越大,創建複雜,盤根錯節的“意大利麪條式代碼”的機率呈指數級的增長。這時候就得面對兩個基礎的問題: 很難真正地對代碼進行封裝, 而系統並沒有對不同部分(也就是 JAR 文件)之間的依賴關係有個明確的概念。每一個公共類都可以被類路徑之下任何其它的公共類所訪問到, 這樣就會導致無意中使用了並不想被公開訪問的 API。此外,類路徑本身也存在問題: 你怎麼知曉所有需要的 JAR 都已經有了, 或者是不是會有重複的項呢? 模塊系統把這倆個問題都給解決了。
模塊化的 JAR 文件都包含一個額外的模塊描述器。在這個模塊描述器中, 對其它模塊的依賴是通過 “requires” 來表示的。另外, “exports” 語句控制着哪些包是可以被其它模塊訪問到的。所有不被導出的包默認都封裝在模塊的裏面。如下是一個模塊描述器的示例,存在於 “module-info.java” 文件中:

我們可以如下展示模塊:

請注意,兩個模塊都包含封裝的包,因爲它們沒有被導出(使用橙色盾牌可視化)。 沒有人會偶然地使用來自這些包中的類。Java 平臺本身也使用自己的模塊系統進行了模塊化。通過封裝 JDK 的內部類,平臺更安全,持續改進也更容易。當啓動一個模塊化應用時, JVM 會驗證是否所有的模塊都能使用,這基於 requires 語句——比脆弱的類路徑邁進了一大步。模塊允許你更好地強制結構化封裝你的應用並明確依賴。

(四):JShel,交互式 Java REPL

許多語言已經具有交互式編程環境,Java 現在加入了這個俱樂部。您可以從控制檯啓動 jshell ,並直接啓動輸入和執行 Java 代碼。 jshell 的即時反饋使它成爲探索 API 和嘗試語言特性的好工具。

測試一個 Java 正則表達式是一個很好的說明 jshell 如何使您的生活更輕鬆的例子。 交互式 shell 還可以提供良好的教學環境以及提高生產力,您可以在此瞭解更多信息。在教人們如何編寫 Java 的過程中,不再需要解釋 “public static void main(String [] args)” 這句廢話。

JShell詳解

JShell項目是第一個官方的Java REPL (Read-Eval-Print-Loop的縮寫,即交互式編程環境),是一種命令行工具。它允許你無需使用類或者方法包裝來執行Java語句。它與Python的解釋器類似,或其它本地支持REPL的JVM語言,如Scala和Groovy。在Java 9新特性中,這絕對是更有趣的特性之一。下面讓我們看看JShell一些最有趣的特性。

1、分號對於純語句是可選的


實際上,在發起的一個關於未來Java特性的調查中,該特性是受多數人認可的。當然分號仍被保留了下來,無論是作爲終結符還是分隔符。REPL允許一次性鍵入純表達式和語句,因此分號對於JShell終端用例是可選的。

2 沒有受檢異常

如果你一直擔心受檢異常會毀掉你的REPL經歷——無需再擔心,JShell在後臺爲你隱藏好了。在下面的例子中,本應當強迫我們捕獲一個IOException,卻沒有出現。下面的例子是我們在讀取和打印一個文件,不需要處理IOException。

有一種情況的確會有受檢異常彈出,就是當我們嘗試運行一個線程,並在裏面使用了 Thread.sleep() 語句。由於這是一個整體的方法而非單獨的純語句,它必須是完全有效的Java語句:

3 Java表達式

JShell終端還可以自己計算Java表達式。字符串連接、方法回調、算法,諸如此類。基本上,任何你可以包裝在 System.out.println(/ expression here /) 裏的都可以計算。正如你可能已經知道到的其它計算方式,它會立即將結果賦給自己的一個變量並打印出來。

4 向前引用

JShell給向前引用提供了很棒的支持,所以你在定義方法時可以引用其他方法或變量,且這些方法或變量僅會在一段時間後被定義。這是AdoptOpenJDK提供的REPL指南中的一個例子:

5 REPL網絡

使用JShell時,我們不會受限於機器和網絡訪問,這帶來了一些有趣的機會。例如,想想把它當做一個終端來與服務器交流,遠程連接到服務器並且從外面控制一些參數。另一個選擇是查詢數據庫,這裏真的是有無限可能。

(五):集合工廠方法

通常,您希望在代碼中創建一個集合(例如,List 或 Set ),並直接用一些元素填充它。 實例化集合,幾個 “add” 調用,使得代碼重複。 Java 9,添加了幾種集合工廠方法:

除了更短和更好閱讀之外,這些方法也可以避免您選擇特定的集合實現。 事實上,從工廠方法返回已放入數個元素的集合實現是高度優化的。這是可能的,因爲它們是不可變的:在創建後,繼續添加元素到這些集合會導致 “UnsupportedOperationException” 。

(六):改進的 Stream API

長期以來,Stream API 都是 Java 標準庫最好的改進之一。通過這套 API 可以在集合上建立用於轉換的申明管道。在 Java 9 中它會變得更好。Stream 接口中添加了 4 個新的方法:dropWhile, takeWhile, ofNullable。還有個 iterate 方法的新重載方法,可以讓你提供一個 Predicate (判斷條件)來指定什麼時候結束迭代:

第二個參數是一個 Lambda,它會在當前 IntStream 中的元素到達 100 的時候返回 true。因此這個簡單的示例是向控制檯打印 1 到 99。
除了對 Stream 本身的擴展,Optional 和 Stream 之間的結合也得到了改進。現在可以通過 Optional 的新方法 stram將一個 Optional 對象轉換爲一個(可能是空的) Stream 對象:

在組合複雜的 Stream 管道時,將 Optional 轉換爲 Stream 非常有用。

(七):多版本兼容JAR

多版本兼容JAR這個特性對於庫的維護者而言是個特別好的消息。當一個新版本的 Java 出現的時候,你的庫用戶要花費數年時間纔會切換到這個新的版本。這就意味着庫得去向後兼容你想要支持的最老的 Java 版本 (許多情況下就是 Java 6 或者 7)。這實際上意味着未來的很長一段時間,你都不能在庫中運用 Java 9 所提供的新特性。幸運的是,多版本兼容 JAR 功能能讓你創建僅在特定版本的 Java 環境中運行庫程序時選擇使用的 class 版本:


在上述場景中, multirelease.jar 可以在 Java 9 中使用, 不過 Helper 這個類使用的不是頂層的 multirelease.Helper 這個 class, 而是處在“META-INF/versions/9”下面的這個。這是特別爲 Java 9 準備的 class 版本,可以運用 Java 9 所提供的特性和庫。同時,在早期的 Java 諸版本中使用這個 JAR 也是能運行的,因爲較老版本的 Java 只會看到頂層的這個 Helper 類。

(八):私有接口方法

Java 8 爲我們帶來了接口的默認方法。 接口現在也可以包含行爲,而不僅僅是方法簽名。 但是,如果在接口上有幾個默認方法,代碼幾乎相同,會發生什麼情況? 通常,您將重構這些方法,調用一個可複用的私有方法。 但默認方法不能是私有的。 將複用代碼創建爲一個默認方法不是一個解決方案,因爲該輔助方法會成爲公共API的一部分。 使用 Java 9,您可以向接口添加私有輔助方法來解決此問題:

如果您使用默認方法開發 API ,那麼私有接口方法可能有助於構建其實現。

(八):HTTP/2

Java 9 中有新的方式來處理 HTTP 調用。這個遲到的特性用於代替老舊的 HttpURLConnection API,並提供對 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所謂的孵化器模塊交付。也就是說,這套 API 不能保證 100% 完成。不過你可以在 Java 9 中開始使用這套 API:

除了這個簡單的請求/響應模型之外,HttpClient 還提供了新的 API 來處理 HTTP/2 的特性,比如流和服務端推送。


轉自:http://swiftlet.net/archives/category/jdk9
發佈了10 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章