- 「MoreThanJava」 宣揚(yáng)的是 「學(xué)習(xí),不止 CODE」,本系列 Java 基礎(chǔ)教程是自己在結(jié)合各方面的知識(shí)之后,對(duì) Java 基礎(chǔ)的一個(gè)總回顧,旨在 「幫助新朋友快速高質(zhì)量的學(xué)習(xí)」。
- 當(dāng)然 不論新老朋友 我相信您都可以 從中獲益。如果覺(jué)得 「不錯(cuò)」 的朋友,歡迎 「關(guān)注 + 留言 + 分享」,文末有完整的獲取鏈接,您的支持是我前進(jìn)的最大的動(dòng)力!
特性總覽
以下是 Java 9 中的引入的部分新特性。關(guān)于 Java 9 新特性更詳細(xì)的介紹可參考這里。
- REPL(JShell)
- 不可變集合的工廠方法
- 模塊系統(tǒng)
- 接口支持私有化
- 鉆石操作符升級(jí)
- Optional 改進(jìn)
- Stream API 改進(jìn)
- 反應(yīng)式流(Reactive Streams)
- 進(jìn)程 API
- 升級(jí)的 Try-With-Resources
- HTTP / 2
- 多版本兼容 Jar 包
- 其他
- 改進(jìn)應(yīng)用安全性能
- 統(tǒng)一 JVM 日志
- G1 設(shè)為默認(rèn)垃圾回收器
- String 底層存儲(chǔ)結(jié)構(gòu)更改
- CompletableFuture API 改進(jìn)
- I/O 流新特性
- JavaScript 引擎 Nashorn 改進(jìn)
- 標(biāo)識(shí)符增加限制
- 改進(jìn)的 Javadoc
- 改進(jìn)的 @Deprectaed 注解
- 多分辨率圖像 API
- 變量句柄
- 改進(jìn)方法句柄(Method Handle)
- 提前編譯 AOT
一. Java 9 REPL(JShell)
什么是 REPL 以及為什么引入
REPL,即 Read-Evaluate-Print-Loop 的簡(jiǎn)稱。由于 Scala 語(yǔ)言的特性和優(yōu)勢(shì)在小型應(yīng)用程序到大型應(yīng)用程序市場(chǎng)大受追捧,于是引來(lái) Oracle 的關(guān)注,并嘗試將大多數(shù) Scala 功能集成到 Java 中。這在 Java 8 中已經(jīng)完成一部分,比如 Lambda 表達(dá)式。
Scala 的最佳功能之一就是 REPL,這是一個(gè)命令行界面和 Scala 解釋器,用于執(zhí)行 Scala 程序。由于并不需要開啟額外的 IDE (就是一個(gè)命令行),它在減少學(xué)習(xí)曲線和簡(jiǎn)化運(yùn)行測(cè)試代碼方面有獨(dú)特的優(yōu)勢(shì)。
于是在 Java 9 中引入了 Java REPL,也稱為 JShell
。
JShell 基礎(chǔ)
打開命令提示符,確保您具有 Java 9 或更高版本,鍵入 jshell
,然后我們就可以開心的使用了。
下面是簡(jiǎn)單示范:
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
工具的幫助部分,請(qǐng)使用/help
命令。要從 jshell
退出,請(qǐng)使用 /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 對(duì)象。
在 Java SE 8 和更早的版本中,如果我們要?jiǎng)?chuàng)建一個(gè)空的 不可變 或 不可修改 的列表,需要借助 Collections
類的 unmodifiableList()
方法才可以:
List<String> list = new ArrayList<>();
list.add("公眾號(hào)");
list.add("我沒(méi)有三顆心臟");
list.add("關(guān)注走起來(lái)");
List<String> immutableList = Collections.unmodifiableList(list);
可以看到,為了創(chuàng)建一個(gè)非空的不可變列表,我們需要經(jīng)歷很多繁瑣和冗長(zhǎng)的步驟。為了克服這一點(diǎn),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 前后的對(duì)比:
// Java 9 之前
List<String> list = new ArrayList<>();
list.add("公眾號(hào)");
list.add("我沒(méi)有三顆心臟");
list.add("關(guān)注走起來(lái)");
List<String> unmodifiableList = Collections.unmodifiableList(list);
// 或者使用 {{}} 的形式
List<String> list = new ArrayList<>() {{
add("公眾號(hào)");
add("我沒(méi)有三顆心臟");
add("關(guān)注走起來(lái)");
}};
List<String> unmodifiableList = Collections.unmodifiableList(list);
// Java 9 便捷的工廠方法
List<String> unmodifiableList = List.of("公眾號(hào)", "我沒(méi)有三顆心臟", "關(guān)注走起來(lái)");
(ps: Set、Map 類似,Map 有兩組方法:of()
和 ofEntries()
分別用于創(chuàng)建 Immutable Map 對(duì)象和 Immutable Map.Entry 對(duì)象)
另外 Java 9 可以直接輸出集合的內(nèi)容,在此之前必須遍歷集合才能全部獲取里面的元素,這是一個(gè)很大的改進(jìn)。
不可變集合的特征
不可變即不可修改。它們通常具有以下幾個(gè)特征:
1、我們無(wú)法添加、修改和刪除其元素;
2、如果嘗試對(duì)它們執(zhí)行添加/刪除/更新操作,將會(huì)得到 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)建,則會(huì)報(bào)出 NullPointerException
異常,如下所示:
jshell> List>String> immutableList = List.of("公眾號(hào)","我沒(méi)有三顆心臟","關(guān)注走起來(lái)", 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 元素,則會(huì)得到 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 的一部分啟動(dòng)了該項(xiàng)目,但是由于進(jìn)行了很大的更改,它被推遲到了 Java SE 8,然后又被推遲了。最終隨著 2017 年 9 月發(fā)布的 Java SE 9 一起發(fā)布。
為什么需要模塊系統(tǒng)?
當(dāng)代碼庫(kù)變得更大時(shí),創(chuàng)建復(fù)雜、糾結(jié)的 “意大利面條代碼” 的幾率成倍增加。在 Java 8 或更早版本交付 Java 應(yīng)用時(shí)存在幾個(gè)基本問(wèn)題:
- 難以真正封裝代碼,并且在系統(tǒng)的不同部分(JAR 文件)之間沒(méi)有顯式依賴關(guān)系的概念。每個(gè)公共類都可以由 classpath 上的任何其他公共類訪問(wèn),從而導(dǎo)致無(wú)意中使用了本不應(yīng)該是公共 API 的類。
- 再者,類路徑本身是有問(wèn)題的:您如何知道是否所有必需的 JAR 都存在,或者是否存在重復(fù)的條目?
- 另外,JDK 太大了,
rt.jar
(rt.jar
就是 Java 基礎(chǔ)類庫(kù)——也就是 Java Doc 里面看到的所有類的 class 文件)等 JAR 文件甚至無(wú)法在小型設(shè)備和應(yīng)用程序中使用:因此我們的應(yīng)用程序和設(shè)備無(wú)法支持更好的性能——打包之后的應(yīng)用程序太大了——也很難測(cè)試和維護(hù)應(yīng)用程序。
模塊系統(tǒng)解決了這幾個(gè)問(wèn)題。
什么是 Java 9 模塊系統(tǒng)?
模塊就是代碼、數(shù)據(jù)和一些資源的自描述集合。它是一組與代碼、數(shù)據(jù)和資源相關(guān)的包。
每個(gè)模塊僅包含一組相關(guān)的代碼和數(shù)據(jù),以支持單一職責(zé)原則(SRP)。
Java 9 模塊系統(tǒng)的主要目標(biāo)就是支持 Java 模塊化編程。(我們將在下面??體驗(yàn)一下模塊化編程)
比較 JDK 8 和 JDK 9
我們知道 JDK 軟件包含什么。安裝 JDK 8 軟件后,我們可以在 Java Home 文件夾中看到幾個(gè)目錄,例如 bin
,jre
,lib
等。
但是,Oracle 在 Java 9 中對(duì)該文件夾結(jié)構(gòu)的更改有些不同,如下所示。
這里的 JDK 9 不包含 JRE。在 JDK 9 中,JRE 分為一個(gè)單獨(dú)的分發(fā)文件夾。JDK 9 軟件包含一個(gè)新文件夾 “ jmods”,它包含一組 Java 9 模塊。在 JDK 9 中,沒(méi)有 rt.jar
和 tools.jar
。(如下所示)
注意: 截止今天, jmods
包含了 95
個(gè)模塊。(最終版可能更多)
比較 Java 8 和 Java 9 應(yīng)用程序
我們已經(jīng)使用 Java 5、Java 6、Java 7 或 Java 8 開發(fā)了許多 Java 應(yīng)用程序了,我們知道 Java 8 或更早版本的應(yīng)用程序,頂級(jí)組件是 Package:
Java 9 應(yīng)用程序與此沒(méi)有太大的區(qū)別。它剛剛引入了稱為 "模塊" 和稱為模塊描述符(module-info.java
)的新組件:
像 Java 8 應(yīng)用程序?qū)?Packages 作為頂級(jí)組件一樣,Java 9 應(yīng)用程序?qū)?Module 作為頂級(jí)組件。
注意:每個(gè) Java 9 模塊只有一個(gè)模塊和一個(gè)模塊描述符。與 Java 8 包不同,我們不能在一個(gè)模塊中創(chuàng)建多個(gè)模塊。
HelloModule 示例程序
作為開發(fā)人員,我們首先從 “HelloWorld” 程序開始學(xué)習(xí)新的概念或編程語(yǔ)言。以同樣的方式,我們開始通過(guò) “ HelloModule” 模塊開發(fā)來(lái)學(xué)習(xí) Java 9 新概念“ 模塊化編程 ”。
第一步:創(chuàng)建一個(gè)空的 Java 項(xiàng)目
如果不想額外命名的話一路 Next 就好了:
第二步:創(chuàng)建 HelloModule 模塊
右鍵項(xiàng)目,創(chuàng)建一個(gè)新的【Module】,命名為:com.wmyskxz.core
并在新 Module 的 src
文件夾下新建包 module.hello
,此時(shí)項(xiàng)目結(jié)構(gòu):
.
└── com.wmyskxz.core
└── src
└── module
└── hello
第三步:編寫 HelloModule.java
在剛才創(chuàng)建的包下新建 HelloModule
文件,并編寫測(cè)試用的代碼:
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
里面的內(nèi)容暴露出去(給其他 Module 使用):
module com.wmyskxz.core {
exports module.hello;
}
module
關(guān)鍵字后面是我們的模塊名稱,里面的 exports
寫明了我們想要暴露出去的包。此時(shí)的文件目錄結(jié)構(gòu):
.
└── com.wmyskxz.core
└── src
├── module
│ └── hello
│ └── HelloModule.java
└── module-info.java
第五步:同樣的方法編寫客戶端
用上面同樣的方法,我們?cè)陧?xiàng)目根目錄創(chuàng)建一個(gè) com.wmyskxz.client
的 Module,并新建 module.client
包目錄,并創(chuàng)建好我們的 HelloModuleClient
文件的大概樣子:
// HelloModuleClient.java
package module.client;
public class HelloModuleClient {
public static void main(String[] args) {
}
}
如果我們想要直接調(diào)用 HelloModule
類,會(huì)發(fā)現(xiàn) IDEA 并沒(méi)有提示信息,也就是說(shuō)我們無(wú)法直接引用了..
我們需要先在模塊描述符(同樣需要在 src
目錄創(chuàng)建 module-info.java
文件)中顯式的引入我們剛才暴露出來(lái)的 com.wmyskxz.core
模塊:
module com.wmyskxz.client {
requires com.wmyskxz.core;
}
(ps:在 IDEA 中編寫完成之后需要手動(dòng) 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();
}
}
此時(shí)的項(xiàng)目結(jié)構(gòu):
.
├── com.wmyskxz.client
│ └── src
│ ├── module
│ │ └── client
│ │ └── HelloModuleClient.java
│ └── module-info.java
└── com.wmyskxz.core
└── src
├── module
│ └── hello
│ └── HelloModule.java
└── module-info.java
第六步:運(yùn)行測(cè)試
運(yùn)行代碼:
Hello Module!
成功!
模塊系統(tǒng)小結(jié)
我們從上面的例子中可以看到,我們可以指定我們想要導(dǎo)出和引用的軟件包,沒(méi)有人可以不小心地使用那些不想被導(dǎo)出的軟件包中的類。
Java 平臺(tái)本身也已經(jīng)使用其自己的模塊系統(tǒng)對(duì) JDK 進(jìn)行了模塊化。啟動(dòng)模塊化應(yīng)用程序時(shí),JVM 會(huì)根據(jù) requires
語(yǔ)句驗(yàn)證是否可以解析所有模塊,這比脆弱的類路徑要安全得多。模塊使您能夠通過(guò)強(qiáng)力執(zhí)行封裝和顯式依賴來(lái)更好地構(gòu)建應(yīng)用程序。
四. 接口支持私有方法
在 Java 8 中,我們可以使用 default
和 static
方法在 Interfaces 中提供方法實(shí)現(xiàn)。但是,我們不能在接口中創(chuàng)建私有方法。
為了避免冗余代碼和提高重用性,Oracle Corp 將在 Java SE 9 接口中引入私有方法。從 Java SE 9 開始,我們就可以使用 private
關(guān)鍵字在接口中編寫私有和私有靜態(tài)方法。
這些私有方法僅與其他類私有方法一樣,它們之間沒(méi)有區(qū)別。以下是演示:
public interface FilterProcess<T> {
// java 7 及以前 特性 全局常量 和抽象方法
public static final String a ="22";
boolean process(T t);
// java 8 特性 靜態(tài)方法和默認(rèn)方法
default void love(){
System.out.println("java8 特性默認(rèn)方法");
}
static void haha(){
System.out.println("java8 特性靜態(tài)方法");
}
// java 9 特性 支持私有方法
private void java9(){}
}
五. 鉆石操作符升級(jí)
我們知道,Java SE 7 引入了一項(xiàng)新功能:Diamond 運(yùn)算符可避免多余的代碼和冗長(zhǎng)的內(nèi)容,從而提高了可讀性。但是,在 Java SE 8 中,Oracle Corp(Java庫(kù)開發(fā)人員)發(fā)現(xiàn)將 Diamond 運(yùn)算符與匿名內(nèi)部類一起使用時(shí)存在一些限制。他們已解決了這些問(wèn)題,并將其作為 Java 9 的一部分發(fā)布。
// java6 及以前
Map<String,String> map7 = new HashMap<String,String>();
// java7 和 8 <> 沒(méi)有了數(shù)據(jù)類型
Map<String,String> map8 = new HashMap<>();
// java9 添加了匿名內(nèi)部類的功能 后面添加了大括號(hào) {} 可以做一些細(xì)節(jié)的操作
Map<String,String> map9 = new HashMap<>(){};
六. Optional 改進(jìn)
在 Java SE 9 中,Oracle Corp 引入了以下三種方法來(lái)改進(jìn) Optional 功能。
-
stream()
; -
ifPresentOrElse()
; or()
可選 stream() 方法
如果給定的 Optional 對(duì)象中存在一個(gè)值,則此 stream()
方法將返回一個(gè)具有該值的順序 Stream。否則,它將返回一個(gè)空流。
Java 9 中添加的stream()
方法允許我們延遲地處理可選對(duì)象,下面是演示:
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 個(gè) 元素,其中只有 2 個(gè)有值。在使用 flatMap 之后,結(jié)果流中包含了 2 個(gè)值。)
可選 ifPresentOrElse() 方法
我們知道,在 Java SE 8 中,我們可以使用 ifPresent()
、isPresent()
和 orElse()
方法來(lái)檢查 Optional 對(duì)象并對(duì)其執(zhí)行功能。這個(gè)過(guò)程有些繁瑣,Java SE 9 引入了一種新的方法來(lái)克服此問(wèn)題。
下面是示例:
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ù)指定默認(rèn)值。下面是 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 改進(jìn)
長(zhǎng)期以來(lái),Streams API 可以說(shuō)是對(duì) Java 標(biāo)準(zhǔn)庫(kù)的最佳改進(jìn)之一。在 Java 9 中,Stream 接口新增加了四個(gè)有用的方法:dropWhile、takeWhile、ofNullable 和 iterate。下面我們來(lái)分別演示一下。
takeWhile() 方法
在 Stream API 中,takeWhile()
方法返回與 Predicate 條件匹配的最長(zhǎng)前綴元素。
它以 Predicate 接口作為參數(shù)。Predicate 是布爾表達(dá)式,它返回 true
或 false
。對(duì)于有序和無(wú)序流,其行為有所不同。讓我們通過(guò)下面的一些簡(jiǎn)單示例對(duì)其進(jìn)行探討。
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
無(wú)序流示例: -
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()
方法在遇到第一個(gè)返回 false
的元素時(shí),它將停止向下遍歷。
dropWhile() 方法
與 takeWhile()
相對(duì)應(yīng),dropWhile()
用于刪除與條件匹配的最長(zhǎng)前綴元素,并返回其余元素。
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
無(wú)序流示例: -
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
(第一個(gè)參數(shù))開頭,匹配 Predicate(第二個(gè)參數(shù)),并使用第三個(gè)參數(shù)生成下一個(gè)元素的元素流。
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
這里,整個(gè)元素流以數(shù)字 2
開始,結(jié)束條件是 < 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()
返回包含單個(gè)元素的順序 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
種方法。
八. 反應(yīng)式流(Reactive Streams)
反應(yīng)式編程的思想最近得到了廣泛的流行。在 Java 平臺(tái)上有流行的反應(yīng)式庫(kù) RxJava 和 Reactor。反應(yīng)式流規(guī)范的出發(fā)點(diǎn)是提供一個(gè)帶非阻塞負(fù)壓( non-blocking backpressure ) 的異步流處理規(guī)范。
Java SE 9 Reactive Streams API 是一個(gè)發(fā)布/訂閱框架,用于實(shí)現(xiàn) Java 語(yǔ)言非常輕松地實(shí)現(xiàn)異步操作,可伸縮和并行應(yīng)用程序。
(從上圖中可以很清楚地看到,Processor既可以作為訂閱服務(wù)器,也可以作為發(fā)布服務(wù)器。)
反應(yīng)式流規(guī)范的核心接口已經(jīng)添加到了 Java9 中的 java.util.concurrent.Flow
類中。
反應(yīng)流示例
讓我們從一個(gè)簡(jiǎn)單的示例開始,在該示例中,我們將實(shí)現(xiàn) Flow API Subscriber 接口并使用 SubmissionPublisher 創(chuàng)建發(fā)布者并發(fā)送消息。
流數(shù)據(jù)
假設(shè)我們有一個(gè) 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 + "]";
}
}
我們還有一個(gè)實(shí)用的工具類,可以為我們創(chuàng)建一個(gè)雇員列表:
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, "我沒(méi)有三顆心臟"),
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
方法中提出請(qǐng)求。 -
counter
變量以保持已處理項(xiàng)目數(shù)的計(jì)數(shù),請(qǐng)注意,其值在onNext
方法中增加了。在我們的main
方法中將使用它來(lái)等待執(zhí)行完成,然后再結(jié)束主線程。 - 在
onSubscribe
方法中調(diào)用訂閱請(qǐng)求以開始處理。還要注意,onNext
在處理完項(xiàng)目后再次調(diào)用該方法,要求發(fā)布者處理下一個(gè)項(xiàng)目。 -
onError
并onComplete
在這里沒(méi)有太多作用,但在現(xiàn)實(shí)世界中的場(chǎng)景,他們應(yīng)該被使用時(shí)出現(xiàn)的錯(cuò)誤或資源的清理成功處理完成時(shí)進(jìn)行糾正措施。
反應(yīng)式流測(cè)試程序
我們將SubmissionPublisher
作為示例使用 Publisher,因此讓我們看一下反應(yīng)流實(shí)現(xiàn)的測(cè)試程序:
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
方法的調(diào)用了。另外,我們應(yīng)該在使用完之后關(guān)閉發(fā)布者,以避免任何內(nèi)存泄漏。
當(dāng)執(zhí)行上述程序時(shí),我們將得到以下輸出:
Subscribed
onSubscribe requested 1 item
Publishing Items to Subscriber
Processing Employee [id=1,name=我沒(méi)有三顆心臟]
Processing Employee [id=2,name=三顆心臟]
Processing Employee [id=3,name=心臟]
Exiting the app
All Processing Done
以上所有代碼均可以在「MoreThanJava」項(xiàng)目下的
demo-project
下找到:傳送門另外,如果您想了解更多內(nèi)容請(qǐng)?jiān)L問(wèn):https://www.journaldev.com/20723/java-9-reactive-streams
九. 進(jìn)程 API
Java 9 增加了 ProcessHandle 接口,可以對(duì)原生進(jìn)程進(jìn)行管理,尤其適合于管理長(zhǎng)時(shí)間運(yùn)行的進(jìn)程。
在使用 ProcessBuilder 來(lái)啟動(dòng)一個(gè)進(jìn)程之后,可以通過(guò) Process.toHandle()
方法來(lái)得到一個(gè) ProcessHandle 對(duì)象的實(shí)例。通過(guò) ProcessHandle 可以獲取到由 ProcessHandle.Info 表示的進(jìn)程的基本信息,如命令行參數(shù)、可執(zhí)行文件路徑和啟動(dòng)時(shí)間等。ProcessHandle 的 onExit()
方法返回一個(gè) CompletableFuture 對(duì)象,可以在進(jìn)程結(jié)束時(shí)執(zhí)行自定義的動(dòng)作。
下面是進(jìn)程 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();
}
});
十. 升級(jí)的 Try-With-Resources
我們知道,Java SE 7 引入了一種新的異常處理結(jié)構(gòu):Try-With-Resources 以自動(dòng)管理資源。這一新聲明的主要目標(biāo)是 “自動(dòng)的更好的資源管理”。
Java SE 9 將對(duì)該語(yǔ)句進(jìn)行一些改進(jìn),以避免更多的冗長(zhǎng)和提高可讀性。
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 調(diào)用的新方法。這種過(guò)期過(guò)期的替代方法是舊的HttpURLConnection
。API 也支持 WebSockets 和 HTTP / 2。需要注意的是:新的 HttpClient API 在 Java 9 中以所謂的 incubator module 的形式提供。這意味著該API尚不能保證最終實(shí)現(xiàn) 100%。盡管如此,隨著Java 9的到來(lái),您已經(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)境中運(yùn)行庫(kù)程序時(shí)選擇使用的 class 版本。
通過(guò) --release 參數(shù)指定編譯版本。
具體的變化就是 META-INF 目錄下 MANIFEST.MF 文件新增了一個(gè)屬性:
Multi-Release: true
然后 META-INF 目錄下還新增了一個(gè) 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,這里不做贅述。
其他更新
改進(jìn)應(yīng)用安全性能
Java 9 新增了 4
個(gè) SHA-3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。另外也增加了通過(guò) java.security.SecureRandom
生成使用 DRBG 算法的強(qiáng)隨機(jī)數(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),可以使用新的命令行選項(xiàng) -Xlog
來(lái)控制 JVM 上所有組件的日志記錄。該日志記錄系統(tǒng)可以設(shè)置輸出的日志消息的標(biāo)簽、級(jí)別、修飾符和輸出目標(biāo)等。
G1 設(shè)為默認(rèn)回收器實(shí)現(xiàn)
Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合(比如 ParNew + SerialOld
),同時(shí)把 G1 設(shè)為默認(rèn)的垃圾回收器實(shí)現(xiàn)(32
位和 64
位系統(tǒng)都是)。 另外,CMS 垃圾回收器已經(jīng)被聲明為廢棄。Java 9 也增加了很多可以通過(guò) jcmd 調(diào)用的診斷命令。
String 底層存儲(chǔ)結(jié)構(gòu)更改
為了對(duì)字符串采用更節(jié)省空間的內(nèi)部表示,String
類的內(nèi)部表示形式從 UTF-16 char
數(shù)組更改為byte
帶有編碼標(biāo)記字段的數(shù)組。新String
類將存儲(chǔ)基于字符串內(nèi)容編碼為 ISO-8859-1 / Latin-1(每個(gè)字符一個(gè)字節(jié))或 UTF-16(每個(gè)字符兩個(gè)字節(jié))的字符。編碼標(biāo)志將指示使用哪種編碼。
(ps: 另外內(nèi)部大部分方法也多了字符編碼的判斷)
CompletableFuture API 的改進(jìn)
在 Java SE 9 中,Oracle Corp 將改進(jìn) CompletableFuture API,以解決 Java SE 8 中提出的一些問(wèn)題。它們將被添加以支持某些延遲和超時(shí),某些實(shí)用程序方法以及更好的子類化。
Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);
這里的 delayExecutor()
是一種靜態(tài)實(shí)用程序方法,用于返回新的 Executor,該 Executor 在給定的延遲后將任務(wù)提交給默認(rèn)的執(zhí)行程序。
I/O 流新特性
類 java.io.InputStream
中增加了新的方法來(lái)讀取和復(fù)制 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 改進(jìn)
Nashorn 是 Java 8 中引入的新的 JavaScript 引擎。Java 9 中的 Nashorn 已經(jīng)實(shí)現(xiàn)了一些 ECMAScript 6 規(guī)范中的新特性,包括模板字符串、二進(jìn)制和八進(jìn)制字面量、迭代器 和 for..of
循環(huán)和箭頭函數(shù)等。Nashorn 還提供了 API 把 ECMAScript 源代碼解析成抽象語(yǔ)法樹( Abstract Syntax Tree,AST ) ,可以用來(lái)對(duì) ECMAScript 源代碼進(jìn)行分析。
標(biāo)識(shí)符增加限制
JDK 8 之前 String _ = "hello;
這樣的標(biāo)識(shí)符可以使用,JDK 9 之后就不允許使用了。
改進(jìn)的 Javadoc
有時(shí)候,微小的事情會(huì)帶來(lái)很大的不同。您是否之前一直像我一樣一直使用 Google 查找正確的 Javadoc 頁(yè)面?現(xiàn)在將不再需要。Javadoc 現(xiàn)在在 API 文檔本身中包含了搜索功能。另外,Javadoc 輸出現(xiàn)在兼容 HTML 5。另外,您會(huì)注意到每個(gè) Javadoc 頁(yè)面都包含有關(guān)類或接口來(lái)自哪個(gè) JDK 模塊的信息。
改進(jìn)的 @Deprecated 注解
注解 @Deprecated
可以標(biāo)記 Java API 狀態(tài),可以是以下幾種:
- 使用它存在風(fēng)險(xiǎn),可能導(dǎo)致錯(cuò)誤
- 可能在未來(lái)版本中不兼容
- 可能在未來(lái)版本中刪除
- 一個(gè)更好和更高效的方案已經(jīng)取代它。
Java 9 中注解增加了兩個(gè)新元素:since 和 forRemoval。
- since: 元素指定已注解的API元素已被棄用的版本。
- forRemoval: 元素表示注解的 API 元素在將來(lái)的版本中被刪除,應(yīng)該遷移 API。
以下實(shí)例為 Java 9 中關(guān)于 Boolean 類的說(shuō)明文檔,文檔中 @Deprecated
注解使用了 since
屬性:Boolean Class。
多分辨率圖像 API
在 Java SE 9 中,Oracle Corp 將引入一個(gè)新的 Multi-Resolution Image API。此 API 中的重要接口是MultiResolutionImage。在 java.awt.image
包中可用。
MultiResolutionImage 封裝了一組具有不同高度和寬度(即不同分辨率)的圖像,并允許我們根據(jù)需求查詢它們。
變量句柄
變量句柄(VarHandle)是對(duì)于一個(gè)變量的強(qiáng)類型引用,或者是一組參數(shù)化定義的變量族,包括了靜態(tài)字段、非靜態(tài)字段、數(shù)組元素等,VarHandle 支持不同訪問(wèn)模型下對(duì)于變量的訪問(wèn),包括簡(jiǎn)單的 read/write
訪問(wèn),volatile read/write
訪問(wèn),以及 CAS 訪問(wèn)。
VarHandle 相比于傳統(tǒng)的對(duì)于變量的并發(fā)操作具有巨大的優(yōu)勢(shì),在 JDK 9 引入了 VarHandle 之后,JUC 包中對(duì)于變量的訪問(wèn)基本上都使用 VarHandle,比如 AQS 中的 CLH 隊(duì)列中使用到的變量等。
改進(jìn)方法句柄(Method Handle)
類 java.lang.invoke.MethodHandles
增加了更多的靜態(tài)方法來(lái)創(chuàng)建不同類型的方法句柄:
-
arrayConstructor:
創(chuàng)建指定類型的數(shù)組。 -
arrayLength:
獲取指定類型的數(shù)組的大小。 -
varHandleInvoker 和 varHandleExactInvoker:
調(diào)用 VarHandle 中的訪問(wèn)模式方法。 -
zero:
返回一個(gè)類型的默認(rèn)值。 -
empty:
返回 MethodType 的返回值類型的默認(rèn)值。 -
loop、countedLoop、iteratedLoop、whileLoop 和 doWhileLoop:
創(chuàng)建不同類型的循環(huán),包括 for 循環(huán)、while 循環(huán) 和 do-while 循環(huán)。 -
tryFinally:
把對(duì)方法句柄的調(diào)用封裝在 try-finally 語(yǔ)句中。
提前編譯 AOT
借助 Java 9,特別是JEP 295,JDK 獲得了提前(ahead-of-time,AOT) 編譯器 jaotc。該編譯器使用 OpenJDK 項(xiàng)目 Graal 進(jìn)行后端代碼生成,這樣做的原因如下:
JIT 編譯器速度很快,但是Java程序可能非常龐大,以至于JIT完全預(yù)熱需要很長(zhǎng)時(shí)間。很少使用的Java方法可能根本不會(huì)被編譯,由于重復(fù)的解釋調(diào)用可能會(huì)導(dǎo)致性能下降
Graal OpenJDK 項(xiàng)目 演示了用純 Java 編寫的編譯器可以生成高度優(yōu)化的代碼。使用此 AOT 編譯器和 Java 9,您可以提前手動(dòng)編譯 Java 代碼。這意味著在執(zhí)行之前生成機(jī)器代碼,而不是像 JIT 編譯器那樣在運(yùn)行時(shí)生成代碼,這是第一種實(shí)驗(yàn)性的方法。
# 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
這將改善啟動(dòng)時(shí)間,因?yàn)?JIT 編譯器不必?cái)r截程序的執(zhí)行。這種方法的主要缺點(diǎn)是生成的機(jī)器代碼依賴于程序所在的平臺(tái)(Linux,MacOS,windows...)。這可能導(dǎo)致 AOT 編譯代碼與特定平臺(tái)綁定。
更多...
完整特性列表: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
文章推薦
- 這都JDK15了,JDK7還不了解? - https://www.wmyskxz.com/2020/08/18/java7-ban-ben-te-xing-xiang-jie/
- 全網(wǎng)最通透的 Java 8 版本特性講解 - https://www.wmyskxz.com/2020/08/19/java8-ban-ben-te-xing-xiang-jie/
- 你記筆記嗎?關(guān)于最近知識(shí)管理工具革新潮心臟有話要說(shuō) - 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/
- 黑莓OS手冊(cè)是如何詳細(xì)闡述底層的進(jìn)程和線程模型的? - 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/
- 「MoreThanJava」系列文集 - https://www.wmyskxz.com/categories/MoreThanJava/
- 本文已收錄至我的 Github 程序員成長(zhǎng)系列 【More Than Java】,學(xué)習(xí),不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
- 個(gè)人公眾號(hào) :wmyskxz,個(gè)人獨(dú)立域名博客:wmyskxz.com,堅(jiān)持原創(chuàng)輸出,下方掃碼關(guān)注,2020,與您共同成長(zhǎng)!
非常感謝各位人才能 看到這里,如果覺(jué)得本篇文章寫得不錯(cuò),覺(jué)得 「我沒(méi)有三顆心臟」有點(diǎn)東西 的話,求點(diǎn)贊,求關(guān)注,求分享,求留言!
創(chuàng)作不易,各位的支持和認(rèn)可,就是我創(chuàng)作的最大動(dòng)力,我們下篇文章見(jiàn)!