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

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

特性總覽

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

  • 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 表達式。java

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

因而在 Java 9 中引入了 Java REPL,也稱爲 JShellgit

JShell 基礎

打開命令提示符,確保您具備 Java 9 或更高版本,鍵入 jshell,而後咱們就能夠開心的使用了。程序員

下面是簡單示範:github

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 中定義和執行類方法:web

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 對象。shell

在 Java SE 8 和更早的版本中,若是咱們要建立一個空的 不可變不可修改 的列表,須要藉助 Collections 類的 unmodifiableList() 方法才能夠:express

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 能夠直接輸出集合的內容,在此以前必須遍歷集合才能所有獲取裏面的元素,這是一個很大的改進。

不可變集合的特徵

不可變即不可修改。它們一般具備如下幾個特徵:

一、咱們沒法添加、修改和刪除其元素;

二、若是嘗試對它們執行添加/刪除/更新操做,將會獲得 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)

三、不可變集合不容許 null 元素;

四、若是嘗試使用 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)

五、若是嘗試添加 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)

六、若是全部元素都是可序列化的,那麼集合是能夠序列化的;

三. 模塊系統

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 五、Java 六、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-22四、SHA3-25六、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, 05);
        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還不瞭解?
  2. 全網最通透的 Java 8 版本特性講解
  3. 你記筆記嗎?關於最近知識管理工具革新潮心臟有話要說
  4. 黑莓OS手冊是如何詳細闡述底層的進程和線程模型的?

  • 本文已收錄至個人 Github 程序員成長系列 【More Than Java】,學習,不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
  • 我的公衆號 :wmyskxz, 我的獨立域名博客:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!

很是感謝各位人才能 看到這裏,若是以爲本篇文章寫得不錯,以爲 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!

創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!


點擊留言

本文分享自微信公衆號 - 我沒有三顆心臟(wmyskxz)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索