作 者:我沒有三顆心臟
原文鏈接:https://mp.weixin.qq.com/s/pR7fY0eK4IEInSCO-JRobw

特性總覽
以下是 JAVA 9 中的引入的部分新特性。關于 Java 9 新特性更詳細的介紹可參考這里。
- REPL(JShell)
- 不可變集合的工廠方法
- 模塊系統(tǒng)
- 接口支持私有化
- 鉆石操作符升級
- Optional 改進
- Stream API 改進
- 反應式流(Reactive Streams)
- 進程 API
- 升級的 Try-With-Resources
- HTTP / 2
- 多版本兼容 Jar 包
- 其他
- 改進應用安全性能
- 統(tǒng)一 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 語言的特性和優(yōu)勢在小型應用程序到大型應用程序市場大受追捧,于是引來 Oracle 的關注,并嘗試將大多數(shù) Scala 功能集成到 Java 中。這在 Java 8 中已經(jīng)完成一部分,比如 Lambda 表達式。
Scala 的最佳功能之一就是 REPL,這是一個命令行界面和 Scala 解釋器,用于執(zhí)行 Scala 程序。由于并不需要開啟額外的 IDE (就是一個命令行),它在減少學習曲線和簡化運行測試代碼方面有獨特的優(yōu)勢。
于是在 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 中定義和執(zhí)行類方法:
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 中增加了一些便捷的工廠方法用于創(chuàng)建 不可變 List、Set、Map 以及 Map.Entry 對象。
在 Java SE 8 和更早的版本中,如果我們要創(chuàng)建一個空的 不可變 或 不可修改 的列表,需要借助 Collections 類的 unmodifiableList() 方法才可以:
List<String> list = new ArrayList<>();
list.add("公眾號");
list.add("我沒有三顆心臟");
list.add("關注走起來");
List<String> immutableList = Collections.unmodifiableList(list);
可以看到,為了創(chuàng)建一個非空的不可變列表,我們需要經(jīng)歷很多繁瑣和冗長的步驟。為了克服這一點,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)
以及可變參數(shù)數(shù)目的方法:
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() 分別用于創(chuàng)建 Immutable Map 對象和 Immutable Map.Entry 對象)
另外 Java 9 可以直接輸出集合的內容,在此之前必須遍歷集合才能全部獲取里面的元素,這是一個很大的改進。
不可變集合的特征
不可變即不可修改。它們通常具有以下幾個特征:
1、我們無法添加、修改和刪除其元素;
2、如果嘗試對它們執(zhí)行添加/刪除/更新操作,將會得到 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 元素創(chuàng)建,則會報出 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、如果所有元素都是可序列化的,那么集合是可以序列化的;
三. 模塊系統(tǒng)
Java 模塊系統(tǒng)是 Oracle 在 Java 9 引入的全新概念。最初,它作為 Java SE 7 Release 的一部分啟動了該項目,但是由于進行了很大的更改,它被推遲到了 Java SE 8,然后又被推遲了。最終隨著 2017 年 9 月發(fā)布的 Java SE 9 一起發(fā)布。
為什么需要模塊系統(tǒng)?
當代碼庫變得更大時,創(chuàng)建復雜、糾結的 “意大利面條代碼” 的幾率成倍增加。在 Java 8 或更早版本交付 Java 應用時存在幾個基本問題:
- 難以真正封裝代碼,并且在系統(tǒng)的不同部分(JAR 文件)之間沒有顯式依賴關系的概念。每個公共類都可以由 classpath 上的任何其他公共類訪問,從而導致無意中使用了本不應該是公共 API 的類。
- 再者,類路徑本身是有問題的:您如何知道是否所有必需的 JAR 都存在,或者是否存在重復的條目?
- 另外,JDK 太大了,rt.jar *(rt.jar 就是 Java 基礎類庫——也就是 Java Doc 里面看到的所有類的 class 文件)*等 JAR 文件甚至無法在小型設備和應用程序中使用:因此我們的應用程序和設備無法支持更好的性能——打包之后的應用程序太大了——也很難測試和維護應用程序。
模塊系統(tǒng)解決了這幾個問題。
什么是 Java 9 模塊系統(tǒng)?
模塊就是代碼、數(shù)據(jù)和一些資源的自描述集合。它是一組與代碼、數(shù)據(jù)和資源相關的包。
每個模塊僅包含一組相關的代碼和數(shù)據(jù),以支持單一職責原則(SRP)。

Java 9 模塊系統(tǒng)的主要目標就是支持 Java 模塊化編程。(我們將在下面體驗一下模塊化編程)
比較 JDK 8 和 JDK 9
我們知道 JDK 軟件包含什么。安裝 JDK 8 軟件后,我們可以在 Java Home 文件夾中看到幾個目錄,例如 bin,jre,lib 等。
但是,Oracle 在 Java 9 中對該文件夾結構的更改有些不同,如下所示。

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

注意: 截止今天, jmods 包含了 95 個模塊。(最終版可能更多)
比較 Java 8 和 Java 9 應用程序
我們已經(jīng)使用 Java 5、Java 6、Java 7 或 Java 8 開發(fā)了許多 Java 應用程序了,我們知道 Java 8 或更早版本的應用程序,頂級組件是 Package:

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

像 Java 8 應用程序將 Packages 作為頂級組件一樣,Java 9 應用程序將 Module 作為頂級組件。
注意:每個 Java 9 模塊只有一個模塊和一個模塊描述符。與 Java 8 包不同,我們不能在一個模塊中創(chuàng)建多個模塊。
HelloModule 示例程序
作為開發(fā)人員,我們首先從 “HelloWorld” 程序開始學習新的概念或編程語言。以同樣的方式,我們開始通過 “ HelloModule” 模塊開發(fā)來學習 Java 9 新概念“ 模塊化編程 ”。
第一步:創(chuàng)建一個空的 Java 項目
如果不想額外命名的話一路 Next 就好了:

第二步:創(chuàng)建 HelloModule 模塊
右鍵項目,創(chuàng)建一個新的【Module】,命名為:com.wmyskxz.core

并在新 Module 的 src 文件夾下新建包 module.hello,此時項目結構:
.
└── com.wmyskxz.core
└── src
└── module
└── hello
第三步:編寫 HelloModule.java
在剛才創(chuàng)建的包下新建 HelloModule 文件,并編寫測試用的代碼:
package module.hello;
public class HelloModule {
public void sayHello() {
System.out.println("Hello Module!");
}
}
第四步:為 Module 編寫模塊描述符
在 IDEA 中,我們可以直接右鍵 src 文件夾,快捷創(chuàng)建 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
第五步:同樣的方法編寫客戶端
用上面同樣的方法,我們在項目根目錄創(chuàng)建一個 com.wmyskxz.client 的 Module,并新建module.client 包目錄,并創(chuàng)建好我們的 HelloModuleClient 文件的大概樣子:
// HelloModuleClient.java
package module.client;
public class HelloModuleClient {
public static void main(String[] args) {
}
}
如果我們想要直接調用 HelloModule 類,會發(fā)現(xiàn) IDEA 并沒有提示信息,也就是說我們無法直接引用了..
我們需要先在模塊描述符(同樣需要在 src 目錄創(chuàng)建 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!
成功!
模塊系統(tǒng)小結
我們從上面的例子中可以看到,我們可以指定我們想要導出和引用的軟件包,沒有人可以不小心地使用那些不想被導出的軟件包中的類。
Java 平臺本身也已經(jīng)使用其自己的模塊系統(tǒng)對 JDK 進行了模塊化。啟動模塊化應用程序時,JVM 會根據(jù) requires 語句驗證是否可以解析所有模塊,這比脆弱的類路徑要安全得多。模塊使您能夠通過強力執(zhí)行封裝和顯式依賴來更好地構建應用程序。
四. 接口支持私有方法
在 Java 8 中,我們可以使用 default 和 static 方法在 Interfaces 中提供方法實現(xiàn)。但是,我們不能在接口中創(chuàng)建私有方法。
為了避免冗余代碼和提高重用性,Oracle Corp 將在 Java SE 9 接口中引入私有方法。從 Java SE 9 開始,我們就可以使用 private 關鍵字在接口中編寫私有和私有靜態(tài)方法。
這些私有方法僅與其他類私有方法一樣,它們之間沒有區(qū)別。以下是演示:
public interface FilterProcess<T> {
// java 7 及以前 特性 全局常量 和抽象方法
public static final String a ="22";
boolean process(T t);
// java 8 特性 靜態(tài)方法和默認方法
default void love(){
System.out.println("java8 特性默認方法");
}
static void haha(){
System.out.println("java8 特性靜態(tài)方法");
}
// java 9 特性 支持私有方法
private void java9(){}
}
五. 鉆石操作符升級
我們知道,Java SE 7 引入了一項新功能:Diamond 運算符可避免多余的代碼和冗長的內容,從而提高了可讀性。但是,在 Java SE 8 中,Oracle Corp(Java庫開發(fā)人員)發(fā)現(xiàn)將 Diamond 運算符與匿名內部類一起使用時存在一些限制。他們已解決了這些問題,并將其作為 Java 9 的一部分發(fā)布。
// java6 及以前
Map<String,String> map7 = new HashMap<String,String>();
// java7 和 8 <> 沒有了數(shù)據(jù)類型
Map<String,String> map8 = new HashMap<>();
// java9 添加了匿名內部類的功能 后面添加了大括號 {} 可以做一些細節(jié)的操作
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 對象并對其執(zhí)行功能。這個過程有些繁瑣,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 作為參數(shù)指定默認值。下面是 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 接口作為參數(shù)。Predicate 是布爾表達式,它返回 true 或 false。對于有序和無序流,其行為有所不同。讓我們通過下面的一些簡單示例對其進行探討。
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(第一個參數(shù))開頭,匹配 Predicate(第二個參數(shù)),并使用第三個參數(shù)生成下一個元素的元素流。
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
這里,整個元素流以數(shù)字 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。反應式流規(guī)范的出發(fā)點是提供一個帶非阻塞負壓( non-blocking backpressure ) 的異步流處理規(guī)范。
Java SE 9 Reactive Streams API 是一個發(fā)布/訂閱框架,用于實現(xiàn) Java 語言非常輕松地實現(xiàn)異步操作,可伸縮和并行應用程序。

(從上圖中可以很清楚地看到,Processor既可以作為訂閱服務器,也可以作為發(fā)布服務器。)
反應式流規(guī)范的核心接口已經(jīng)添加到了 Java9 中的 java.util.concurrent.Flow 類中。
反應流示例
讓我們從一個簡單的示例開始,在該示例中,我們將實現(xiàn) Flow API Subscriber 接口并使用 SubmissionPublisher 創(chuàng)建發(fā)布者并發(fā)送消息。
流數(shù)據(jù)
假設我們有一個 Employee 類,它將用于創(chuàng)建要從發(fā)布者發(fā)送到訂閱者的流消息。
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 + "]";
}
}
我們還有一個實用的工具類,可以為我們創(chuàng)建一個雇員列表:
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變量以保持已處理項目數(shù)的計數(shù),請注意,其值在 onNext 方法中增加了。在我們的 main 方法中將使用它來等待執(zhí)行完成,然后再結束主線程。
- 在onSubscribe方法中調用訂閱請求以開始處理。還要注意,onNext在處理完項目后再次調用該方法,要求發(fā)布者處理下一個項目。
- onError并onComplete在這里沒有太多作用,但在現(xiàn)實世界中的場景,他們應該被使用時出現(xiàn)的錯誤或資源的清理成功處理完成時進行糾正措施。
反應式流測試程序
我們將SubmissionPublisher作為示例使用 Publisher,因此讓我們看一下反應流實現(xiàn)的測試程序:
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");
}
}
上面代碼中最重要的部分就是 subscribe 和 submit 方法的調用了。另外,我們應該在使用完之后關閉發(fā)布者,以避免任何內存泄漏。
當執(zhí)行上述程序時,我們將得到以下輸出:
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 表示的進程的基本信息,如命令行參數(shù)、可執(zhí)行文件路徑和啟動時間等。ProcessHandle 的 onExit() 方法返回一個 CompletableFuture 對象,可以在進程結束時執(zhí)行自定義的動作。
下面是進程 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 提供了一種執(zhí)行 HTTP 調用的新方法。這種過期過期的替代方法是舊的HttpURLConnection。API 也支持 WebSockets 和 HTTP / 2。需要注意的是:新的 HttpClient API 在 Java 9 中以所謂的 incubator module 的形式提供。這意味著該API尚不能保證最終實現(xiàn) 100%。盡管如此,隨著Java 9的到來,您已經(jīng)可以開始使用此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 功能能讓你創(chuàng)建僅在特定版本的 Java 環(huán)境中運行庫程序時選擇使用的 class 版本。
通過 --release 參數(shù)指定編譯版本。
具體的變化就是 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 算法的強隨機數(shù)。下面給出了 SHA-3 哈希算法的使用示例:
final MessageDigest instance = MessageDigest.getInstance("SHA3-224");
final byte[] digest = instance.digest("".getBytes());
System.out.println(Hex.encodeHexString(digest));
統(tǒng)一 JVM 日志
Java 9 中 ,JVM 有了統(tǒng)一的日志記錄系統(tǒng),可以使用新的命令行選項 -Xlog 來控制 JVM 上所有組件的日志記錄。該日志記錄系統(tǒng)可以設置輸出的日志消息的標簽、級別、修飾符和輸出目標等。
G1 設為默認回收器實現(xiàn)
Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合(比如 ParNew + SerialOld),同時把 G1 設為默認的垃圾回收器實現(xiàn)(32 位和 64 位系統(tǒng)都是)。另外,CMS 垃圾回收器已經(jīng)被聲明為廢棄。Java 9 也增加了很多可以通過 jcmd 調用的診斷命令。
String 底層存儲結構更改

String 底層從 char[] 數(shù)組換位了 byte[]
為了對字符串采用更節(jié)省空間的內部表示,String類的內部表示形式從 UTF-16 char數(shù)組更改為byte帶有編碼標記字段的數(shù)組。新String類將存儲基于字符串內容編碼為 ISO-8859-1 / Latin-1(每個字符一個字節(jié))或 UTF-16(每個字符兩個字節(jié))的字符。編碼標志將指示使用哪種編碼。
(ps: 另外內部大部分方法也多了字符編碼的判斷)
CompletableFuture API 的改進
在 Java SE 9 中,Oracle Corp 將改進 CompletableFuture API,以解決 Java SE 8 中提出的一些問題。它們將被添加以支持某些延遲和超時,某些實用程序方法以及更好的子類化。
Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);
這里的 delayExecutor() 是一種靜態(tài)實用程序方法,用于返回新的 Executor,該 Executor 在給定的延遲后將任務提交給默認的執(zhí)行程序。
I/O 流新特性
類 java.io.InputStream 中增加了新的方法來讀取和復制 InputStream 中包含的數(shù)據(jù)。
- readAllBytes:讀取 InputStream 中的所有剩余字節(jié)。
- readNBytes:從 InputStream 中讀取指定數(shù)量的字節(jié)到數(shù)組中。
- transferTo:讀取 InputStream 中的全部字節(jié)并寫入到指定的 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 已經(jīng)實現(xiàn)了一些 ECMAScript 6 規(guī)范中的新特性,包括模板字符串、二進制和八進制字面量、迭代器 和 for..of 循環(huán)和箭頭函數(shù)等。Nashorn 還提供了 API 把 ECMAScript 源代碼解析成抽象語法樹( Abstract Syntax Tree,AST ) ,可以用來對 ECMAScript 源代碼進行分析。
標識符增加限制
JDK 8 之前 String _ = "hello; 這樣的標識符可以使用,JDK 9 之后就不允許使用了。
改進的 Javadoc
有時候,微小的事情會帶來很大的不同。您是否之前一直像我一樣一直使用 Google 查找正確的 Javadoc 頁面?現(xiàn)在將不再需要。Javadoc 現(xiàn)在在 API 文檔本身中包含了搜索功能。另外,Javadoc 輸出現(xiàn)在兼容 HTML 5。另外,您會注意到每個 Javadoc 頁面都包含有關類或接口來自哪個 JDK 模塊的信息。

改進的 @Deprecated 注解
注解 @Deprecated 可以標記 Java API 狀態(tài),可以是以下幾種:
- 使用它存在風險,可能導致錯誤
- 可能在未來版本中不兼容
- 可能在未來版本中刪除
- 一個更好和更高效的方案已經(jīng)取代它。
Java 9 中注解增加了兩個新元素:since 和 forRemoval。
- 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 封裝了一組具有不同高度和寬度(即不同分辨率)的圖像,并允許我們根據(jù)需求查詢它們。
變量句柄
變量句柄(VarHandle)是對于一個變量的強類型引用,或者是一組參數(shù)化定義的變量族,包括了靜態(tài)字段、非靜態(tài)字段、數(shù)組元素等,VarHandle 支持不同訪問模型下對于變量的訪問,包括簡單的 read/write 訪問,volatile read/write 訪問,以及 CAS 訪問。
VarHandle 相比于傳統(tǒng)的對于變量的并發(fā)操作具有巨大的優(yōu)勢,在 JDK 9 引入了 VarHandle 之后,JUC 包中對于變量的訪問基本上都使用 VarHandle,比如 AQS 中的 CLH 隊列中使用到的變量等。
改進方法句柄(Method Handle)
類 java.lang.invoke.MethodHandles 增加了更多的靜態(tài)方法來創(chuàng)建不同類型的方法句柄:
- arrayConstructor: 創(chuàng)建指定類型的數(shù)組。
- arrayLength: 獲取指定類型的數(shù)組的大小。
- varHandleInvoker 和 varHandleExactInvoker: 調用 VarHandle 中的訪問模式方法。
- zero: 返回一個類型的默認值。
- empty: 返回 MethodType 的返回值類型的默認值。
- loop、countedLoop、iteratedLoop、whileLoop 和 doWhileLoop: 創(chuàng)建不同類型的循環(huán),包括 for 循環(huán)、while 循環(huán) 和 do-while 循環(huán)。
- 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 編寫的編譯器可以生成高度優(yōu)化的代碼。使用此 AOT 編譯器和 Java 9,您可以提前手動編譯 Java 代碼。這意味著在執(zhí)行之前生成機器代碼,而不是像 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 編譯器不必攔截程序的執(zhí)行。這種方法的主要缺點是生成的機器代碼依賴于程序所在的平臺(linux,macOS,windows...)。這可能導致 AOT 編譯代碼與特定平臺綁定。

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