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

我沒有三顆心臟發表於2020-08-20

  • 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 「幫助新朋友快速高質量的學習」
  • 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關注 + 留言 + 分享」,文末有完整的獲取連結,您的支援是我前進的最大的動力!

特性總覽

以下是 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,然後我們就可以開心的使用了。

下面是簡單示範:

wmyskxz:~ wmyskxz$ 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

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

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

jshell> Hello.sayHello()
Hello
jshell> 

Java REPL - 幫助和退出

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

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

jshell> /exit
|  Goodbye
wmyskxz:~ wmyskxz$ 

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

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

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

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

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

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

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

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

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

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

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

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

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

不可變集合的特徵

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

1、我們無法新增、修改和刪除其元素;

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

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

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

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

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)

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

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

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)。

Java 9 模組系統的主要目標就是支援 Java 模組化程式設計(我們將在下面?體驗一下模組化程式設計)

比較 JDK 8 和 JDK 9

我們知道 JDK 軟體包含什麼。安裝 JDK 8 軟體後,我們可以在 Java Home 資料夾中看到幾個目錄,例如 binjrelib 等。

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

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

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

比較 Java 8 和 Java 9 應用程式

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

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

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

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

HelloModule 示例程式

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

第一步:建立一個空的 Java 專案

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

第二步:建立 HelloModule 模組

右鍵專案,建立一個新的【Module】,命名為:com.wmyskxz.core

並在新 Module 的 src 資料夾下新建包 module.hello,此時專案結構:

.
└── com.wmyskxz.core
    └── src
        └── module
            └── hello

第三步:編寫 HelloModule.java

在剛才建立的包下新建 HelloModule 檔案,並編寫測試用的程式碼:

package module.hello;

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

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

在 IDEA 中,我們可以直接右鍵 src 資料夾,快捷建立 module-info.java 檔案:

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

module com.wmyskxz.core {
    exports module.hello;
}

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

.
└── com.wmyskxz.core
    └── src
        ├── module
        │   └── hello
        │       └── HelloModule.java
        └── module-info.java

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

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

// HelloModuleClient.java
package module.client;

public class HelloModuleClient {

    public static void main(String[] args) {

    }
}

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

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

module com.wmyskxz.client {
    requires com.wmyskxz.core;
}

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

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

package module.client;

import module.hello.HelloModule;

public class HelloModuleClient {

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

此時的專案結構:

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

第六步:執行測試

執行程式碼:

Hello Module!

成功!

模組系統小結

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

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

四. 介面支援私有方法

在 Java 8 中,我們可以使用 defaultstatic 方法在 Interfaces 中提供方法實現。但是,我們不能在介面中建立私有方法。

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

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

public interface FilterProcess<T> {

    // 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(){}
}

五. 鑽石操作符升級

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

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

六. Optional 改進

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

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

可選 stream() 方法

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

Java 9 中新增的stream() 方法允許我們延遲地處理可選物件,下面是演示:

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

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

可選 ifPresentOrElse() 方法

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

下面是示例:

jshell> Optional<Integer> 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<Integer> opt2 = Optional.empty()
opt2 ==> Optional.empty

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

可選 or() 方法

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

public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

下面是有值情況的演示:

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

jshell> import java.util.function.*

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

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

下面是為空情況的演示:

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

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

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

七. Stream API 改進

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

takeWhile() 方法

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

它以 Predicate 介面作為引數。Predicate 是布林表示式,它返回 truefalse。對於有序和無序流,其行為有所不同。讓我們通過下面的一些簡單示例對其進行探討。

Stream API 定義:

default Stream<T> takeWhile(Predicate<? super T> predicate)

有序流示例: -

jshell> Stream<Integer> 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 < 4).forEach(a -> System.out.println(a))
1
2
3

無序流示例: -

jshell> Stream<Integer> 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 < 4).forEach(a -> System.out.println(a))
1
2

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

dropWhile() 方法

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

Stream API 定義:

default Stream<T> dropWhile(Predicate<? super T> predicate)

有序流示例: -

jshell> Stream<Integer> 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 < 4).forEach(a -> System.out.println(a))
4
5
6
7
8
9
10

無序流示例: -

jshell> Stream<Integer> 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 < 4).forEach(a -> System.out.println(a))
4
5
3
6
7
8
9
10

iterate() 方法

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

Stream API 定義:

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

IntStream 迭代示例: -

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

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

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

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

ofNullable() 方法

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

Java SE 9 示例: -

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

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

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

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

jshell>

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

八. 反應式流(Reactive Streams)

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

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

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

反應式流規範的核心介面已經新增到了 Java9 中的 java.util.concurrent.Flow 類中。

反應流示例

讓我們從一個簡單的示例開始,在該示例中,我們將實現 Flow API Subscriber 介面並使用 SubmissionPublisher 建立釋出者併傳送訊息。

流資料

假設我們有一個 Employee 類,它將用於建立要從釋出者傳送到訂閱者的流訊息。

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 + "]";
    }
}

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

package com.wmyskxz.reactive.streams;

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

public class EmpHelper {

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

訂閱者

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<Employee> {

    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;
    }
}
  • Subscription變數以保留引用,以便可以在onNext方法中提出請求。
  • counter變數以保持已處理專案數的計數,請注意,其值在 onNext 方法中增加了。在我們的 main 方法中將使用它來等待執行完成,然後再結束主執行緒。
  • onSubscribe方法中呼叫訂閱請求以開始處理。還要注意,onNext在處理完專案後再次呼叫該方法,要求釋出者處理下一個專案。
  • onErroronComplete在這裡沒有太多作用,但在現實世界中的場景,他們應該被使用時出現的錯誤或資源的清理成功處理完成時進行糾正措施。

反應式流測試程式

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

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<Employee> publisher = new SubmissionPublisher<>();

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

        List<Employee> 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");
    }
}

上面程式碼中最重要的部分就是 subscribesubmit 方法的呼叫了。另外,我們應該在使用完之後關閉釋出者,以避免任何記憶體洩漏。

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

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

以上所有程式碼均可以在「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 的使用示例:

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();
    }
});

十. 升級的 Try-With-Resources

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

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

Java SE 7示例

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

Java SE 9示例:

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

十一. HTTP / 2

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

HttpClient client = HttpClient.newHttpClient();

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


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

十二. 多版本相容 Jar 包

多版本相容 JAR 功能能讓你建立僅在特定版本的 Java 環境中執行庫程式時選擇使用的 class 版本。

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

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

Multi-Release: true

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

multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class

具體的例子可以在這裡檢視到: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 雜湊演算法的使用示例:

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

統一 JVM 日誌

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

G1 設為預設回收器實現

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

String 底層儲存結構更改

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 中提出的一些問題。它們將被新增以支援某些延遲和超時,某些實用程式方法以及更好的子類化。

Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);

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

I/O 流新特性

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

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

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

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());
    }
}

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 模組的資訊。

改進的 @Deprecated 註解

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

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

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

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

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

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 佇列中使用到的變數等。

瞭解更多戳這裡:https://kknews.cc/code/amqz5on.html

改進方法控制程式碼(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 編譯器那樣在執行時生成程式碼,這是第一種實驗性的方法。

# 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

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

瞭解更多戳這裡:https://juejin.im/post/6850418120570437646

更多...

完整特性列表: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

文章推薦

  1. 這都JDK15了,JDK7還不瞭解? - https://www.wmyskxz.com/2020/08/18/java7-ban-ben-te-xing-xiang-jie/
  2. 全網最通透的 Java 8 版本特性講解 - https://www.wmyskxz.com/2020/08/19/java8-ban-ben-te-xing-xiang-jie/
  3. 你記筆記嗎?關於最近知識管理工具革新潮心臟有話要說 - https://www.wmyskxz.com/2020/08/16/ni-ji-bi-ji-ma-guan-yu-zui-jin-zhi-shi-guan-li-gong-ju-ge-xin-chao-xin-zang-you-hua-yao-shuo/
  4. 黑莓OS手冊是如何詳細闡述底層的程式和執行緒模型的? - https://www.wmyskxz.com/2020/07/31/hao-wen-tui-jian-hei-mei-os-shou-ce-shi-ru-he-xiang-xi-chan-shu-di-ceng-de-jin-cheng-he-xian-cheng-mo-xing-de/
  5. 「MoreThanJava」系列文集 - https://www.wmyskxz.com/categories/MoreThanJava/
  • 本文已收錄至我的 Github 程式設計師成長系列 【More Than Java】,學習,不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
  • 個人公眾號 :wmyskxz,個人獨立域名部落格:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!

非常感謝各位人才能 看到這裡,如果覺得本篇文章寫得不錯,覺得 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!

相關文章