Java9這些史詩級更新你都不知道?Java9新特性一文打盡

作 者:我沒有三顆心臟

原文鏈接:https://mp.weixin.qq.com/s/pR7fY0eK4IEInSCO-JRobw

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

特性總覽

以下是 Java 9 中的引入的部分新特性。關於 Java 9 新特性更詳細的介紹可參考這裡。

  • REPL(JShell)
  • 不可變集合的工廠方法
  • 模塊系統
  • 接口支持私有化
  • 鑽石操作符升級
  • Optional 改進
  • Stream API 改進
  • 反應式流(Reactive Streams)
  • 進程 API
  • 升級的 Try-With-Resources
  • HTTP / 2
  • 多版本兼容 Jar 包
  • 其他
    • 改進應用安全性能
    • 統一 JVM 日誌
    • G1 設為默認垃圾回收器
    • String 底層存儲結構更改
    • CompletableFuture API 改進
    • I/O 流新特性
    • JavaScript 引擎 Nashorn 改進
    • 標識符增加限制
    • 改進的 Javadoc
    • 改進的 @Deprectaed 註解
    • 多分辨率圖像 API
    • 變量句柄
    • 改進方法句柄(Method Handle)
    • 提前編譯 AOT

一. Java 9 REPL(JShell)

什麼是 REPL 以及為什麼引入

REPL,即 Read-Evaluate-Print-Loop 的簡稱。由於 Scala 語言的特性和優勢在小型應用程序到大型應用程序市場大受追捧,於是引來 Oracle 的關注,並嘗試將大多數 Scala 功能集成到 Java 中。這在 Java 8 中已經完成一部分,比如 Lambda 表達式。

Scala 的最佳功能之一就是 REPL,這是一個命令行界面和 Scala 解釋器,用於執行 Scala 程序。由於並不需要開啟額外的 IDE (就是一個命令行),它在減少學習曲線和簡化運行測試代碼方面有獨特的優勢。

於是在 Java 9 中引入了 Java REPL,也稱為 JShell。

JShell 基礎

打開命令提示符,確保您具有 Java 9 或更高版本,鍵入 jshell,然後我們就可以開心的使用了。

下面是簡單示範:

<code>wmyskxz:~ wmyskxznbsp;jshell 
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell> 

jshell> System.out.println("Hello World");
Hello World

jshell> String str = "Hello JShell!"
str ==> "Hello JShell!"

jshell> str
str ==> "Hello JShell!"

jshell> System.out.println(str)
Hello JShell!

jshell> int counter = 0
counter ==> 0

jshell> counter++
$6 ==> 0

jshell> counter
counter ==> 1

jshell> counter+5
$8 ==> 6
/<code>

也可以在 Java Shell 中定義和執行類方法:

<code>jshell> class Hello {
   ...> public static void sayHello() {
   ...> System.out.print("Hello");
   ...> }
   ...> }
|  created class Hello

jshell> Hello.sayHello()
Hello
jshell> 
/<code>

Java REPL - 幫助和退出

要獲得 jshell 工具的幫助部分,請使用/help命令。要從 jshell 退出,請使用 /exit命令 (或者直接使用 Ctrl + D 命令退出)。

<code>jshell> /help
|  Type a Java language expression, statement, or declaration.
|  Or type one of the following commands:
|  /list [|-all|-start]
|   list the source you have typed
|  /edit 
...

jshell> /exit
|  Goodbye
wmyskxz:~ wmyskxznbsp;
/<code>

二. 不可變集合的工廠方法

Java 9 中增加了一些便捷的工廠方法用於創建 不可變 List、Set、Map 以及 Map.Entry 對象。

在 Java SE 8 和更早的版本中,如果我們要創建一個空的 不可變不可修改 的列表,需要藉助 Collections 類的 unmodifiableList() 方法才可以:

<code>List list = new ArrayList<>();
list.add("公眾號");
list.add("我沒有三顆心臟");
list.add("關注走起來");
List immutableList = Collections.unmodifiableList(list);
/<code>

可以看到,為了創建一個非空的不可變列表,我們需要經歷很多繁瑣和冗長的步驟。為了克服這一點,Java 9 在 List 接口中引入了以下有用的重載方法:

<code>static  List of(E e1)
static  List of(E e1,E e2) 
static  List of(E e1,E e2,E e3)
static  List of(E e1,E e2,E e3,E e4)
static  List of(E e1,E e2,E e3,E e4,E e5) 
static  List of(E e1,E e2,E e3,E e4,E e5,E e6) 
static  List of(E e1,E e2,E e3,E e4,E e5,E e6,E e7) 
static  List of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8) 
static  List of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9) 
static  List of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9,E e10)/<code>

以及可變參數數目的方法:

<code>static  List of(E... elements)  /<code>

可以看到 Java 9 前後的對比:

<code>// Java 9 之前
List list = new ArrayList<>();
list.add("公眾號");
list.add("我沒有三顆心臟");
list.add("關注走起來");
List unmodifiableList = Collections.unmodifiableList(list);
// 或者使用 {{}} 的形式
List list = new ArrayList<>() {{
    add("公眾號");
    add("我沒有三顆心臟");
    add("關注走起來");
}};
List unmodifiableList = Collections.unmodifiableList(list);

// Java 9 便捷的工廠方法
List unmodifiableList = List.of("公眾號", "我沒有三顆心臟", "關注走起來");
/<code> 

(ps: Set、Map 類似,Map 有兩組方法:of() 和 ofEntries() 分別用於創建 Immutable Map 對象和 Immutable Map.Entry 對象)

另外 Java 9 可以直接輸出集合的內容,在此之前必須遍歷集合才能全部獲取裡面的元素,這是一個很大的改進。

不可變集合的特徵

不可變即不可修改。它們通常具有以下幾個特徵:

1、我們無法添加、修改和刪除其元素;

2、如果嘗試對它們執行添加/刪除/更新操作,將會得到 UnsupportedOperationException 異常,如下所示:

<code>jshell> immutableList.add("Test")
|  java.lang.UnsupportedOperationException thrown: 
|        at ImmutableCollections.uoe (ImmutableCollections.java:68)
|        at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:74)
|        at (#2:1)/<code>

3、不可變集合不允許 null 元素;

4、如果嘗試使用 null 元素創建,則會報出 NullPointerException 異常,如下所示:

<code>jshell> List>String> immutableList = List.of("公眾號","我沒有三顆心臟","關注走起來", null)
|  java.lang.NullPointerException thrown: 
|        at Objects.requireNonNull (Objects.java:221)
|        at ImmutableCollections$ListN. (ImmutableCollections.java:179)
|        at List.of (List.java:859)
|        at (#4:1)/<code>

5、如果嘗試添加 null 元素,則會得到 UnsupportedOperationException 異常,如下所示:

<code>jshell> immutableList.add(null)
|  java.lang.UnsupportedOperationException thrown: 
|        at ImmutableCollections.uoe (ImmutableCollections.java:68)
|        at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:74)
|        at (#3:1)/<code>

6、如果所有元素都是可序列化的,那麼集合是可以序列化的;

三. 模塊系統

Java 模塊系統是 Oracle 在 Java 9 引入的全新概念。最初,它作為 Java SE 7 Release 的一部分啟動了該項目,但是由於進行了很大的更改,它被推遲到了 Java SE 8,然後又被推遲了。最終隨著 2017 年 9 月發佈的 Java SE 9 一起發佈。

為什麼需要模塊系統?

當代碼庫變得更大時,創建複雜、糾結的 “意大利麵條代碼” 的幾率成倍增加。在 Java 8 或更早版本交付 Java 應用時存在幾個基本問題:

  1. 難以真正封裝代碼,並且在系統的不同部分(JAR 文件)之間沒有顯式依賴關係的概念。每個公共類都可以由 classpath 上的任何其他公共類訪問,從而導致無意中使用了本不應該是公共 API 的類。
  2. 再者,類路徑本身是有問題的:您如何知道是否所有必需的 JAR 都存在,或者是否存在重複的條目?
  3. 另外,JDK 太大了
    ,rt.jar *(rt.jar 就是 Java 基礎類庫——也就是 Java Doc 裡面看到的所有類的 class 文件)*等 JAR 文件甚至無法在小型設備和應用程序中使用:因此我們的應用程序和設備無法支持更好的性能——打包之後的應用程序太大了——也很難測試和維護應用程序。

模塊系統解決了這幾個問題。

什麼是 Java 9 模塊系統?

模塊就是代碼、數據和一些資源的自描述集合。它是一組與代碼、數據和資源相關的包。

每個模塊僅包含一組相關的代碼和數據,以支持單一職責原則(SRP)。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

Java 9 模塊系統的主要目標就是支持 Java 模塊化編程。(我們將在下面體驗一下模塊化編程)

比較 JDK 8 和 JDK 9

我們知道 JDK 軟件包含什麼。安裝 JDK 8 軟件後,我們可以在 Java Home 文件夾中看到幾個目錄,例如 bin,jre,lib 等。

但是,Oracle 在 Java 9 中對該文件夾結構的更改有些不同,如下所示。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

這裡的 JDK 9 不包含 JRE。在 JDK 9 中,JRE 分為一個單獨的分發文件夾。JDK 9 軟件包含一個新文件夾 “ jmods”,它包含一組 Java 9 模塊。在 JDK 9 中,沒有 rt.jar 和 tools.jar。(如下所示)

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

注意: 截止今天, jmods 包含了 95 個模塊。(最終版可能更多)

比較 Java 8 和 Java 9 應用程序

我們已經使用 Java 5、Java 6、Java 7 或 Java 8 開發了許多 Java 應用程序了,我們知道 Java 8 或更早版本的應用程序,頂級組件是 Package:

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

Java 9 應用程序與此沒有太大的區別。它剛剛引入了稱為 "模塊" 和稱為模塊描述符(module-info.java)的新組件:

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

像 Java 8 應用程序將 Packages 作為頂級組件一樣,Java 9 應用程序將 Module 作為頂級組件。

注意:每個 Java 9 模塊只有一個模塊和一個模塊描述符。與 Java 8 包不同,我們不能在一個模塊中創建多個模塊。

HelloModule 示例程序

作為開發人員,我們首先從 “HelloWorld” 程序開始學習新的概念或編程語言。以同樣的方式,我們開始通過 “ HelloModule” 模塊開發來學習 Java 9 新概念“ 模塊化編程 ”。

第一步:創建一個空的 Java 項目

如果不想額外命名的話一路 Next 就好了:

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

第二步:創建 HelloModule 模塊

右鍵項目,創建一個新的【Module】,命名為:com.wmyskxz.core

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

並在新 Module 的 src 文件夾下新建包 module.hello,此時項目結構:

<code>.
└── com.wmyskxz.core
    └── src
        └── module
            └── hello
/<code>

第三步:編寫 HelloModule.java

在剛才創建的包下新建 HelloModule 文件,並編寫測試用的代碼:

<code>package module.hello;

public class HelloModule {
  
    public void sayHello() {
        System.out.println("Hello Module!");
    }
}
/<code>

第四步:為 Module 編寫模塊描述符

在 IDEA 中,我們可以直接右鍵 src 文件夾,快捷創建 module-info.java 文件:

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

編寫 module-info.java 文件,將我們剛才的包 module.hello 裡面的內容暴露出去(給其他 Module 使用):

<code>module com.wmyskxz.core {
    exports module.hello;
}
/<code>

module 關鍵字後面是我們的模塊名稱,裡面的 exports 寫明瞭我們想要暴露出去的包。此時的文件目錄結構:

<code>.
└── com.wmyskxz.core
    └── src
        ├── module
        │   └── hello
        │       └── HelloModule.java
        └── module-info.java
/<code>

第五步:同樣的方法編寫客戶端

用上面同樣的方法,我們在項目根目錄創建一個 com.wmyskxz.client 的 Module,並新建module.client 包目錄,並創建好我們的 HelloModuleClient 文件的大概樣子:

<code>// HelloModuleClient.java
package module.client;

public class HelloModuleClient {

    public static void main(String[] args) {

    }
}/<code>

如果我們想要直接調用 HelloModule 類,會發現 IDEA 並沒有提示信息,也就是說我們無法直接引用了..

我們需要先在模塊描述符(同樣需要在 src 目錄創建 module-info.java 文件)中顯式的引入我們剛才暴露出來的 com.wmyskxz.core 模塊:

<code>module com.wmyskxz.client {
    requires com.wmyskxz.core;
}
/<code>

(ps:在 IDEA 中編寫完成之後需要手動 alt + enter 引入模塊依賴)

這一步完成之後,我們就可以在剛才的 HelloModuleClient 中愉快的使用 HelloModule 文件了:

<code>package module.client;

import module.hello.HelloModule;

public class HelloModuleClient {

    public static void main(String[] args) {
        HelloModule helloModule = new HelloModule();
        helloModule.sayHello();
    }
}/<code>

此時的項目結構:

<code>.
├── com.wmyskxz.client
│   └── src
│       ├── module
│       │   └── client
│       │       └── HelloModuleClient.java
│       └── module-info.java
└── com.wmyskxz.core
    └── src
        ├── module
        │   └── hello
        │       └── HelloModule.java
        └── module-info.java/<code>

第六步:運行測試

運行代碼:

<code>Hello Module!/<code>

成功!

模塊系統小結

我們從上面的例子中可以看到,我們可以指定我們想要導出和引用的軟件包,沒有人可以不小心地使用那些不想被導出的軟件包中的類。

Java 平臺本身也已經使用其自己的模塊系統對 JDK 進行了模塊化。啟動模塊化應用程序時,JVM 會根據 requires 語句驗證是否可以解析所有模塊,這比脆弱的類路徑要安全得多。模塊使您能夠通過強力執行封裝和顯式依賴來更好地構建應用程序。

四. 接口支持私有方法

在 Java 8 中,我們可以使用 default 和 static 方法在 Interfaces 中提供方法實現。但是,我們不能在接口中創建私有方法。

為了避免冗餘代碼和提高重用性,Oracle Corp 將在 Java SE 9 接口中引入私有方法。從 Java SE 9 開始,我們就可以使用 private 關鍵字在接口中編寫私有和私有靜態方法。

這些私有方法僅與其他類私有方法一樣,它們之間沒有區別。以下是演示:

<code>public interface FilterProcess {

    // java 7 及以前 特性  全局常量 和抽象方法
    public static final String a ="22";
    boolean process(T t);

    // java 8 特性 靜態方法和默認方法
    default void love(){
        System.out.println("java8 特性默認方法");
    }
    static void haha(){
        System.out.println("java8 特性靜態方法");
    }

    // java 9 特性 支持私有方法
    private void java9(){}
}/<code>

五. 鑽石操作符升級

我們知道,Java SE 7 引入了一項新功能:Diamond 運算符可避免多餘的代碼和冗長的內容,從而提高了可讀性。但是,在 Java SE 8 中,Oracle Corp(Java庫開發人員)發現將 Diamond 運算符與匿名內部類一起使用時存在一些限制。他們已解決了這些問題,並將其作為 Java 9 的一部分發布。

<code>// java6 及以前
Map map7 = new HashMap();
// java7 和 8 <> 沒有了數據類型
Map map8 = new HashMap<>();
// java9 添加了匿名內部類的功能 後面添加了大括號 {}  可以做一些細節的操作
Map map9 = new HashMap<>(){};
/<code>

六. Optional 改進

在 Java SE 9 中,Oracle Corp 引入了以下三種方法來改進 Optional 功能。

  • stream();
  • ifPresentOrElse();
  • or()

可選 stream() 方法

如果給定的 Optional 對象中存在一個值,則此 stream() 方法將返回一個具有該值的順序 Stream。否則,它將返回一個空流。

Java 9 中添加的stream() 方法允許我們延遲地處理可選對象,下面是演示:

<code>jshell> long count = Stream.of(
   ...>     Optional.of(1),
   ...>     Optional.empty(),
   ...>     Optional.of(2)
   ...> ).flatMap(Optional::stream)
   ...>     .count();
   ...> System.out.println(count);
   ...>
count ==> 2
2
/<code>

(Optiona l 流中包含 3 個 元素,其中只有 2 個有值。在使用 flatMap 之後,結果流中包含了 2 個值。)

可選 ifPresentOrElse() 方法

我們知道,在 Java SE 8 中,我們可以使用 ifPresent()、isPresent() 和 orElse() 方法來檢查 Optional 對象並對其執行功能。這個過程有些繁瑣,Java SE 9 引入了一種新的方法來克服此問題。

下面是示例:

<code>jshell> Optional opt1 = Optional.of(4)
opt1 ==> Optional[4]

jshell> opt1.ifPresentOrElse( x -> System.out.println("Result found: " + x), () -> System.out.println("Not Found."))
Result found: 4

jshell> Optional opt2 = Optional.empty()
opt2 ==> Optional.empty

jshell> opt2.ifPresentOrElse( x -> System.out.println("Result found: " + x), () -> System.out.println("Not Found."))
Not Found./<code>

可選 or() 方法

在 Java SE 9 中,使用 or() 方法便捷的返回值。如果 Optional 包含值,則直接返回原值,否則就返回指定的值。or() 方法將 Supplier 作為參數指定默認值。下面是 API 的定義:

<code>public Optional or(Supplier extends Optional extends T>> supplier)/<code>

下面是有值情況的演示:

<code>jshell> Optional opStr = Optional.of("Rams")
opStr ==> Optional[Rams]

jshell> import java.util.function.*

jshell> Supplier> supStr = () -> Optional.of("No Name")
supStr ==> $Lambda$67/222624801@23faf8f2

jshell> opStr.or(supStr)
$5 ==> Optional[Rams]/<code>

下面是為空情況的演示:

<code>jshell> Optional opStr = Optional.empty()
opStr ==> Optional.empty

jshell> Supplier> supStr = () -> Optional.of("No Name")
supStr ==> $Lambda$67/222624801@23faf8f2

jshell> opStr.or(supStr)
$7 ==> Optional[No Name]/<code>

七. Stream API 改進

長期以來,Streams API 可以說是對 Java 標準庫的最佳改進之一。在 Java 9 中,Stream 接口新增加了四個有用的方法:dropWhile、takeWhile、ofNullable 和 iterate。下面我們來分別演示一下。

takeWhile() 方法

在 Stream API 中,takeWhile() 方法返回與 Predicate 條件匹配的最長前綴元素。

它以 Predicate 接口作為參數。Predicate 是布爾表達式,它返回 true 或 false。對於有序和無序流,其行為有所不同。讓我們通過下面的一些簡單示例對其進行探討。

Stream API 定義:

<code>default Stream takeWhile(Predicate super T> predicate)/<code> 

有序流示例:-

<code>jshell> Stream stream = Stream.of(1,2,3,4,5,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.takeWhile(x -> x  System.out.println(a))
1
2
3/<code>

無序流示例:-

<code>jshell> Stream stream = Stream.of(1,2,4,5,3,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.takeWhile(x -> x  System.out.println(a))
1
2
/<code>

從上面的例子中我們可以看出,takeWhile() 方法在遇到第一個返回 false 的元素時,它將停止向下遍歷。

dropWhile() 方法

與 takeWhile() 相對應,dropWhile() 用於刪除與條件匹配的最長前綴元素,並返回其餘元素。

Stream API 定義:

<code>default Stream dropWhile(Predicate super T> predicate)/<code>

有序流示例:-

<code>jshell> Stream stream = Stream.of(1,2,3,4,5,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.dropWhile(x -> x  System.out.println(a))
4
5
6
7
8
9
10/<code>

無序流示例:-

<code>jshell> Stream stream = Stream.of(1,2,4,5,3,6,7,8,9,10)
stream ==> java.util.stream.ReferencePipeline$Head@55d56113

jshell> stream.dropWhile(x -> x  System.out.println(a))
4
5
3
6
7
8
9
10/<code>

iterate() 方法

在 Stream API 中,iterate() 方法能夠返回以 initialValue(第一個參數)開頭,匹配 Predicate(第二個參數),並使用第三個參數生成下一個元素的元素流。

Stream API 定義:

<code>static  Stream iterate(T seed, Predicate super T> hasNext, UnaryOperator next)/<code>

IntStream 迭代示例:-

<code>jshell> IntStream.iterate(2, x -> x  x * x).forEach(System.out::println)
2
4
16/<code>

這裡,整個元素流以數字 2 開始,結束條件是 < 20,並且在下一次迭代中,遞增值是自身值的平方。

而這在 Java SE 8 中需要輔助 filter 條件才能完成:

<code>jshell> IntStream.iterate(2, x -> x * x).filter(x -> x /<code>

ofNullable() 方法

在 Stream API 中,ofNullable() 返回包含單個元素的順序 Stream(如果非null),否則返回空 Stream。

Java SE 9 示例:-

<code>jshell> Stream s = Stream.ofNullable(1)
s ==> java.util.stream.ReferencePipeline$Head@1e965684

jshell> s.forEach(System.out::println)
1

jshell> Stream s = Stream.ofNullable(null)
s ==> java.util.stream.ReferencePipeline$Head@3b088d51

jshell> s.forEach(System.out::println)

jshell>/<code>

注意:Stream 的子接口(如 IntStream、LongStream 等..)都繼承了上述的 4 種方法。

八. 反應式流(Reactive Streams)

反應式編程的思想最近得到了廣泛的流行。在 Java 平臺上有流行的反應式庫 RxJava 和 Reactor。反應式流規範的出發點是提供一個帶非阻塞負壓( non-blocking backpressure ) 的異步流處理規範。

Java SE 9 Reactive Streams API 是一個發佈/訂閱框架,用於實現 Java 語言非常輕鬆地實現異步操作,可伸縮和並行應用程序。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

(從上圖中可以很清楚地看到,Processor既可以作為訂閱服務器,也可以作為發佈服務器。)

反應式流規範的核心接口已經添加到了 Java9 中的 java.util.concurrent.Flow 類中。

反應流示例

讓我們從一個簡單的示例開始,在該示例中,我們將實現 Flow API Subscriber 接口並使用 SubmissionPublisher 創建發佈者併發送消息。

流數據

假設我們有一個 Employee 類,它將用於創建要從發佈者發送到訂閱者的流消息。

<code>package com.wmyskxz.reactive.beans;

public class Employee {

    private int id;
    private String name;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Employee(int i, String s) {
        this.id = i;
        this.name = s;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "[id=" + id + ",name=" + name + "]";
    }
}
/<code>

我們還有一個實用的工具類,可以為我們創建一個僱員列表:

<code>package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.List;

public class EmpHelper {

    public static List getEmps() {
        return List.of(
            new Employee(1, "我沒有三顆心臟"),
            new Employee(2, "三顆心臟"),
            new Employee(3, "心臟")
        );
    }
}
/<code>

訂閱者

<code>package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;

public class MySubscriber implements Subscriber {

    private Subscription subscription;

    private int counter = 0;

    @Override
    public void onSubscribe(Subscription subscription) {
        System.out.println("Subscribed");
        this.subscription = subscription;
        this.subscription.request(1); // requesting data from publisher
        System.out.println("onSubscribe requested 1 item");
    }

    @Override
    public void onNext(Employee item) {
        System.out.println("Processing Employee " + item);
        counter++;
        this.subscription.request(1);
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("Some error happened");
        e.printStackTrace();
    }

    @Override
    public void onComplete() {
        System.out.println("All Processing Done");
    }

    public int getCounter() {
        return counter;
    }
}/<code>
  • Subscription變量以保留引用,以便可以在onNext方法中提出請求。
  • counter變量以保持已處理項目數的計數,請注意,其值在 onNext 方法中增加了。在我們的 main 方法中將使用它來等待執行完成,然後再結束主線程。
  • 在onSubscribe方法中調用訂閱請求以開始處理。還要注意,onNext在處理完項目後再次調用該方法,要求發佈者處理下一個項目。
  • onError並onComplete在這裡沒有太多作用,但在現實世界中的場景,他們應該被使用時出現的錯誤或資源的清理成功處理完成時進行糾正措施。

反應式流測試程序

我們將SubmissionPublisher作為示例使用 Publisher,因此讓我們看一下反應流實現的測試程序:

<code>package com.wmyskxz.reactive.streams;

import com.wmyskxz.reactive.beans.Employee;
import java.util.List;
import java.util.concurrent.SubmissionPublisher;

public class MyReactiveApp {

    public static void main(String[] args) throws InterruptedException {

        // Create Publisher
        SubmissionPublisher publisher = new SubmissionPublisher<>();

        // Register Subscriber
        MySubscriber subs = new MySubscriber();
        publisher.subscribe(subs);

        List emps = EmpHelper.getEmps();

        // Publish items
        System.out.println("Publishing Items to Subscriber");
        for (Employee employee : emps) {
            publisher.submit(employee);
            Thread.sleep(1000);// simulate true environment
        }

        // logic to wait till processing of all messages are over
        while (emps.size() != subs.getCounter()) {
            Thread.sleep(10);
        }
        // close the Publisher
        publisher.close();

        System.out.println("Exiting the app");
    }
}
/<code>

上面代碼中最重要的部分就是 subscribe 和 submit 方法的調用了。另外,我們應該在使用完之後關閉發佈者,以避免任何內存洩漏。

當執行上述程序時,我們將得到以下輸出:

<code>Subscribed
onSubscribe requested 1 item
Publishing Items to Subscriber
Processing Employee [id=1,name=我沒有三顆心臟]
Processing Employee [id=2,name=三顆心臟]
Processing Employee [id=3,name=心臟]
Exiting the app
All Processing Done/<code> 

以上所有代碼均可以在「MoreThanJava」項目下的 demo-project 下找到:傳送門

另外,如果您想了解更多內容請訪問:https://www.journaldev.com/20723/java-9-reactive-streams

九. 進程 API

Java 9 增加了 ProcessHandle 接口,可以對原生進程進行管理,尤其適合於管理長時間運行的進程。

在使用 ProcessBuilder 來啟動一個進程之後,可以通過 Process.toHandle() 方法來得到一個 ProcessHandle 對象的實例。通過 ProcessHandle 可以獲取到由 ProcessHandle.Info 表示的進程的基本信息,如命令行參數、可執行文件路徑和啟動時間等。ProcessHandle 的 onExit() 方法返回一個 CompletableFuture 對象,可以在進程結束時執行自定義的動作。

下面是進程 API 的使用示例:

<code>final ProcessBuilder processBuilder = new ProcessBuilder("top")
    .inheritIO();
final ProcessHandle processHandle = processBuilder.start().toHandle();
processHandle.onExit().whenCompleteAsync((handle, throwable) -> {
    if (throwable == null) {
        System.out.println(handle.pid());
    } else {
        throwable.printStackTrace();
    }
});/<code>

十. 升級的 Try-With-Resources

我們知道,Java SE 7 引入了一種新的異常處理結構:Try-With-Resources 以自動管理資源。這一新聲明的主要目標是 “自動的更好的資源管理”。

Java SE 9 將對該語句進行一些改進,以避免更多的冗長和提高可讀性。

Java SE 7示例

<code>void testARM_Before_Java9() throws IOException{
   BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
   try (BufferedReader reader2 = reader1) {
     System.out.println(reader2.readLine());
   }
}/<code>

Java SE 9示例:

<code>void testARM_Java9() throws IOException{
   BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
   try (reader1) {
     System.out.println(reader1.readLine());
   }
}/<code>

十一. HTTP / 2

Java 9 提供了一種執行 HTTP 調用的新方法。這種過期過期的替代方法是舊的HttpURLConnection。API 也支持 WebSockets 和 HTTP / 2。需要注意的是:新的 HttpClient API 在 Java 9 中以所謂的 incubator module 的形式提供。這意味著該API尚不能保證最終實現 100%。儘管如此,隨著Java 9的到來,您已經可以開始使用此API:

<code>HttpClient client = HttpClient.newHttpClient();

HttpRequest req =
   HttpRequest.newBuilder(URI.create("http://www.google.com"))
              .header("User-Agent","Java")
              .GET()
              .build();


HttpResponse resp = client.send(req, HttpResponse.BodyHandler.asString());
/<code>

十二. 多版本兼容 Jar 包

多版本兼容 JAR 功能能讓你創建僅在特定版本的 Java 環境中運行庫程序時選擇使用的 class 版本。

通過 --release 參數指定編譯版本。

具體的變化就是 META-INF 目錄下 MANIFEST.MF 文件新增了一個屬性:

<code>Multi-Release: true/<code>

然後 META-INF 目錄下還新增了一個 versions 目錄,如果是要支持 Java 9,則在versions 目錄下有 9 的目錄。

<code>multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class/<code>

具體的例子可以在這裡查看到:https://www.runoob.com/java/java9-multirelease-jar.html,這裡不做贅述。

其他更新

改進應用安全性能

Java 9 新增了 4 個 SHA-3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。另外也增加了通過 java.security.SecureRandom 生成使用 DRBG 算法的強隨機數。下面給出了 SHA-3 哈希算法的使用示例:

<code>final MessageDigest instance = MessageDigest.getInstance("SHA3-224");
final byte[] digest = instance.digest("".getBytes());
System.out.println(Hex.encodeHexString(digest));/<code>

統一 JVM 日誌

Java 9 中 ,JVM 有了統一的日誌記錄系統,可以使用新的命令行選項 -Xlog 來控制 JVM 上所有組件的日誌記錄。該日誌記錄系統可以設置輸出的日誌消息的標籤、級別、修飾符和輸出目標等。

G1 設為默認回收器實現

Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合(比如 ParNew + SerialOld),同時把 G1 設為默認的垃圾回收器實現(32 位和 64 位系統都是)。另外,CMS 垃圾回收器已經被聲明為廢棄。Java 9 也增加了很多可以通過 jcmd 調用的診斷命令。

String 底層存儲結構更改

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

String 底層從 char[] 數組換位了 byte[]

為了對字符串採用更節省空間的內部表示,String類的內部表示形式從 UTF-16 char數組更改為byte帶有編碼標記字段的數組。新String類將存儲基於字符串內容編碼為 ISO-8859-1 / Latin-1(每個字符一個字節)或 UTF-16(每個字符兩個字節)的字符。編碼標誌將指示使用哪種編碼。

(ps: 另外內部大部分方法也多了字符編碼的判斷)

CompletableFuture API 的改進

在 Java SE 9 中,Oracle Corp 將改進 CompletableFuture API,以解決 Java SE 8 中提出的一些問題。它們將被添加以支持某些延遲和超時,某些實用程序方法以及更好的子類化。

<code>Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);
/<code>

這裡的 delayExecutor() 是一種靜態實用程序方法,用於返回新的 Executor,該 Executor 在給定的延遲後將任務提交給默認的執行程序。

I/O 流新特性

類 java.io.InputStream 中增加了新的方法來讀取和複製 InputStream 中包含的數據。

  • readAllBytes:讀取 InputStream 中的所有剩餘字節。
  • readNBytes:從 InputStream 中讀取指定數量的字節到數組中。
  • transferTo:讀取 InputStream 中的全部字節並寫入到指定的 OutputStream 中 。

下面是新方法的使用示例:

<code>public class TestInputStream {
    private InputStream inputStream;
    private static final String CONTENT = "Hello World";
    @Before
    public void setUp() throws Exception {
        this.inputStream =
            TestInputStream.class.getResourceAsStream("/input.txt");
    }
    @Test
    public void testReadAllBytes() throws Exception {
        final String content = new String(this.inputStream.readAllBytes());
        assertEquals(CONTENT, content);
    }
    @Test
    public void testReadNBytes() throws Exception {
        final byte[] data = new byte[5];
        this.inputStream.readNBytes(data, 0, 5);
        assertEquals("Hello", new String(data));
    }
    @Test
    public void testTransferTo() throws Exception {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        this.inputStream.transferTo(outputStream);
        assertEquals(CONTENT, outputStream.toString());
    }
}
/<code>

JavaScript 引擎 Nashorn 改進

Nashorn 是 Java 8 中引入的新的 JavaScript 引擎。Java 9 中的 Nashorn 已經實現了一些 ECMAScript 6 規範中的新特性,包括模板字符串、二進制和八進制字面量、迭代器 和 for..of 循環和箭頭函數等。Nashorn 還提供了 API 把 ECMAScript 源代碼解析成抽象語法樹( Abstract Syntax Tree,AST ) ,可以用來對 ECMAScript 源代碼進行分析。

標識符增加限制

JDK 8 之前 String _ = "hello; 這樣的標識符可以使用,JDK 9 之後就不允許使用了。

改進的 Javadoc

有時候,微小的事情會帶來很大的不同。您是否之前一直像我一樣一直使用 Google 查找正確的 Javadoc 頁面?現在將不再需要。Javadoc 現在在 API 文檔本身中包含了搜索功能。另外,Javadoc 輸出現在兼容 HTML 5。另外,您會注意到每個 Javadoc 頁面都包含有關類或接口來自哪個 JDK 模塊的信息。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

改進的 @Deprecated 註解

註解 @Deprecated 可以標記 Java API 狀態,可以是以下幾種:

  • 使用它存在風險,可能導致錯誤
  • 可能在未來版本中不兼容
  • 可能在未來版本中刪除
  • 一個更好和更高效的方案已經取代它。

Java 9 中註解增加了兩個新元素:sinceforRemoval

  • since: 元素指定已註解的API元素已被棄用的版本。
  • forRemoval: 元素表示註解的 API 元素在將來的版本中被刪除,應該遷移 API。

以下實例為 Java 9 中關於 Boolean 類的說明文檔,文檔中 @Deprecated 註解使用了since 屬性:Boolean Class。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

JavaDoc 關於 Boolean 的說明截取

多分辨率圖像 API

在 Java SE 9 中,Oracle Corp 將引入一個新的 Multi-Resolution Image API。此 API 中的重要接口是MultiResolutionImage。在 java.awt.image 包中可用。

MultiResolutionImage 封裝了一組具有不同高度和寬度(即不同分辨率)的圖像,並允許我們根據需求查詢它們。

變量句柄

變量句柄(VarHandle)是對於一個變量的強類型引用,或者是一組參數化定義的變量族,包括了靜態字段、非靜態字段、數組元素等,VarHandle 支持不同訪問模型下對於變量的訪問,包括簡單的 read/write 訪問,volatile read/write 訪問,以及 CAS 訪問。

VarHandle 相比於傳統的對於變量的併發操作具有巨大的優勢,在 JDK 9 引入了 VarHandle 之後,JUC 包中對於變量的訪問基本上都使用 VarHandle,比如 AQS 中的 CLH 隊列中使用到的變量等。

改進方法句柄(Method Handle)

類 java.lang.invoke.MethodHandles 增加了更多的靜態方法來創建不同類型的方法句柄:

  • arrayConstructor: 創建指定類型的數組。
  • arrayLength: 獲取指定類型的數組的大小。
  • varHandleInvoker 和 varHandleExactInvoker: 調用 VarHandle 中的訪問模式方法。
  • zero: 返回一個類型的默認值。
  • empty: 返回 MethodType 的返回值類型的默認值。
  • loop、countedLoop、iteratedLoop、whileLoop 和 doWhileLoop: 創建不同類型的循環,包括 for 循環、while 循環 和 do-while 循環。
  • tryFinally: 把對方法句柄的調用封裝在 try-finally 語句中。

提前編譯 AOT

藉助 Java 9,特別是JEP 295,JDK 獲得了提前(ahead-of-time,AOT) 編譯器 jaotc。該編譯器使用 OpenJDK 項目 Graal 進行後端代碼生成,這樣做的原因如下:

JIT 編譯器速度很快,但是Java程序可能非常龐大,以至於JIT完全預熱需要很長時間。很少使用的Java方法可能根本不會被編譯,由於重複的解釋調用可能會導致性能下降

原文鏈接:openjdk.java.net/jeps/295

Graal OpenJDK 項目 演示了用純 Java 編寫的編譯器可以生成高度優化的代碼。使用此 AOT 編譯器和 Java 9,您可以提前手動編譯 Java 代碼。這意味著在執行之前生成機器代碼,而不是像 JIT 編譯器那樣在運行時生成代碼,這是第一種實驗性的方法。

<code># using the new AOT compiler (jaotc is bundeled within JDK 9 and above)
jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base
 
# with Java 9 you have to manually specify the location of the native code
java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld
/<code>

這將改善啟動時間,因為 JIT 編譯器不必攔截程序的執行。這種方法的主要缺點是生成的機器代碼依賴於程序所在的平臺(Linux,MacOS,windows...)。這可能導致 AOT 編譯代碼與特定平臺綁定。

Java9這些史詩級更新你都不知道?Java9新特性一文打盡

更多...

完整特性列表:https://openjdk.java.net/projects/jdk9/

參考資料

  1. OpenJDK 官方文檔 - https://openjdk.java.net/projects/jdk9/
  2. Java 9 Modules | JournalDev - https://www.journaldev.com/13106/java-9-modules
  3. JDK 9 新特性詳解 - https://my.oschina.net/mdxlcj/blog/1622984
  4. Java SE 9:Stream API Improvements - https://www.journaldev.com/13204/javase9-stream-api-improvements
  5. 9 NEW FEATURES IN JAVA 9 - https://www.pluralsight.com/blog/software-development/java-9-new-features
  6. Java 9 新特性概述 | IBM - https://developer.ibm.com/zh/articles/the-new-features-of-Java-9/
  7. Java 9 多版本兼容 jar 包 | 菜鳥教程 - https://www.runoob.com/java/java9-multirelease-jar.html


分享到:


相關文章: