經(jīng)過4次跳票,歷經(jīng)曲折的java 9 終于終于在2017年9月21日發(fā)布(距離上個(gè)版本足足3年半時(shí)間)
java 9 提供了超過 150 項(xiàng)新功能特性,包括備受期待的模塊化系統(tǒng)、可交互的 REPL 工具:jshell,JDK 編譯工具,Java 公共 API 和私有代碼,以及安全增強(qiáng)、擴(kuò)展提升、性能管理改善等。可以說 Java 9 是一個(gè)龐大的系統(tǒng)工程,完全做了一個(gè)整體改變。但本博文只介紹最重要的十大新特性
特性列表
- 平臺級modularity(原名:Jigsaw) 模塊化系統(tǒng)
- Java 的 REPL 工具: jShell 命令
- 多版本兼容 jar 包(這個(gè)在處理向下兼容方面,非常好用)
- 語法改進(jìn):接口的私有方法
- 語法改進(jìn):UnderScore(下劃線)使用的限制
- 底層結(jié)構(gòu):String 存儲結(jié)構(gòu)變更(這個(gè)很重要)
- 集合工廠方法:快速創(chuàng)建只讀集合
- 增強(qiáng)的 Stream API
- 全新的 HTTP 客戶端 API
- 其它特性
它的新特性來自于100于項(xiàng)JEP和40于項(xiàng)JSR
1. 平臺級modularity(原名:Jigsaw) 模塊化系統(tǒng)
模塊化系統(tǒng)Java7開始籌備,Java8進(jìn)行了大量工作,Java9才落地。首先帶來最直觀的感受,就是目錄結(jié)構(gòu)的感受:
JDK8以及以前版本:


而Java9的結(jié)構(gòu)目錄:


對目錄做相應(yīng)的介紹:

Java 9 的定義功能是一套全新的模塊系統(tǒng)。當(dāng)代碼庫越來越大,創(chuàng)建復(fù)雜,盤根錯(cuò)節(jié)的**“意大利面條式代碼”的幾率呈指數(shù)級的增長**。這時(shí)候就得面對兩個(gè)基礎(chǔ)的問題: 很難真正地對代碼進(jìn)行封裝, 而系統(tǒng)并沒有對不同部分(也就是 JAR 文件)之間的依賴關(guān)系有個(gè)明確的概念。每一個(gè)公共類都可以被類路徑之下任何其它的公共類所訪問到, 這樣就會導(dǎo)致無意中使用了并不想被公開訪問的 API。此外,類路徑本身也存在問題: 你怎么知曉所有需要的 JAR 都已經(jīng)有了, 或者是不是會有重復(fù)的項(xiàng)呢? 模塊系統(tǒng)把這倆個(gè)問題都給解決了。
在模塊的 src 下創(chuàng)建 module-info.java 文件,來描述依賴和導(dǎo)出(暴露)。
requires:指明對其它模塊的依賴。
exports:控制著哪些包可以被其它模塊訪問到。所有不被導(dǎo)出的包
默認(rèn)都被封裝在模塊里面。
2、Java 的 REPL 工具: jShell 命令
REPL:read - evaluate - print - loop
這個(gè)簡單的說就是能想腳本語言那樣,所見即所得。之前我們用java,哪怕只想輸出一句hello world,都是非常麻煩的。需要建文件、寫代碼、編譯、運(yùn)行等等?,F(xiàn)在有了jShell工具,實(shí)在太方便了

這樣我就進(jìn)入了jshell環(huán)境。下面Hello World就是這么簡單了

- jShell 也可以從文件中加載語句或者將語句保存到文件中(使用Open命令)
- jShell 也可以是 tab 鍵進(jìn)行自動補(bǔ)全和自動添加分號

列出當(dāng)前 session 里所有有效的代碼片段:/list
3、多版本兼容 jar 包(這個(gè)在處理向下兼容方面,非常好用)
當(dāng)一個(gè)新版本的 Java 出現(xiàn)的時(shí)候,你的庫用戶要花費(fèi)數(shù)年時(shí)間才會
切換到這個(gè)新的版本。這就意味著庫得去向后兼容你想要支持的最老
的 Java 版本(許多情況下就是 Java 6 或者 Java7)。這實(shí)際上意味著
未來的很長一段時(shí)間,你都不能在庫中運(yùn)用 Java 9 所提供的新特性。
幸運(yùn)的是,多版本兼容 jar 功能能讓你創(chuàng)建僅在特定版本的 Java 環(huán)境
中運(yùn)行庫程序選擇使用的 class 版本
案例:略
4、語法改進(jìn):接口的私有方法
在 Java 9 中,接口更加的靈活和強(qiáng)大,連方法的訪問權(quán)限修飾符
都可以聲明為 private 的了,此時(shí)方法將不會成為你對外暴露的 API
的一部分(個(gè)人認(rèn)為,這肯定是JDK8遺漏了的一個(gè)點(diǎn),哈哈)
看個(gè)例子:
public static String staticFun() {
privateFun();
return "";
}
default String defaultFun() {
privateFun();
return "";
}
private static void privateFun() {
System.out.println("我是私有方法~");
}
這樣子是沒有問題,可以正常調(diào)用和使用的。但是需要注意一下兩點(diǎn)
- 私有方法可以是static,也可以不是。看你需要default方法調(diào)用還是static方法調(diào)用
- 私有方法只能用private修飾,不能用protected。若不寫,默認(rèn)就是public,就是普通靜態(tài)方法了。
default String defaultFun() {
privateFun();
return "";
}
private void privateFun() {
System.out.println("我是私有方法~");
}
4、語法改進(jìn):鉆石操作符(Diamond Operator)使用升級 泛型
在 java 8 中如下的操作是會報(bào)錯(cuò)的:
public static void main(String[] args) {
Set<String> set1 = new HashSet<>(); //最常用的初始化
//Set<String> set2 = new HashSet<>(){}; //在JDK8中報(bào)錯(cuò)
Set<String> set2 = new HashSet<String>(){}; //這樣在JDK8中也正常
Set<String> set3 = new HashSet<String>(){{}}; //這樣也都是正常的
}
由此課件,報(bào)錯(cuò)的那種情況是因?yàn)樵贘DK8中,還不能直接推斷出鉆石操作符里面的類型而報(bào)錯(cuò)。而我們在JDK9以后,就可以直接這么寫了:
public static void main(String[] args) {
Set<String> set1 = new HashSet<>(); //最常用的初始化
Set<String> set2 = new HashSet<>(){}; //在JDK8中報(bào)錯(cuò)
Set<String> set3 = new HashSet<>(){{}}; //這樣也都是正常的
}
這樣寫都是不會報(bào)錯(cuò),可以直接書寫使用的。相當(dāng)于直接創(chuàng)建了一個(gè)HashMap的子類。
5、語法改進(jìn):UnderScore(下劃線)使用的限制
這個(gè)點(diǎn)非常的小。距離說明就懂了
在Java8中,我們給變量取名直接就是_
public static void main(String[] args) {
String _ = "hello";
System.out.println(_); //hello
}

我們很清晰的看到,Java8其實(shí)給出了提示,但是編譯運(yùn)行都是能通過的,而到了Java9:

直接就提示_是關(guān)鍵字,編譯都過不了了。
6、底層結(jié)構(gòu):String 存儲結(jié)構(gòu)變更(這個(gè)很重要)
UTF-8表示一個(gè)字符是個(gè)動態(tài)的過程,可以能用1、2、3個(gè)字節(jié)都是有可能的。但是UTF-16明確的就是不管你是拉丁文、中文等,都是恒定的用兩個(gè)字節(jié)表示
JDK8的字符串存儲在char類型的數(shù)組里面,不難想象在絕大多數(shù)情況下,char類型只需要一個(gè)字節(jié)就能表示出來了,比如各種字母什么的,兩個(gè)字節(jié)存儲勢必會浪費(fèi)空間,JDK9的一個(gè)優(yōu)化就在這,內(nèi)存的優(yōu)化。
Java8:
private final char value[];
Java9:
private final byte[] value;
結(jié)論:String 再也不用 char[] 來存儲啦,改成了 byte[] 加上編碼標(biāo)
記,節(jié)約了不少空間。由于底層用了字節(jié)數(shù)組byte[]來存儲,所以遇上非拉丁文,JDK9配合了一個(gè)encodingFlag來配合編碼解碼的
so,相應(yīng)的StringBuffer 和 StringBuilder 也對應(yīng)的做出了對應(yīng)的變化。
有的人擔(dān)心,這會不會影響到我的charAt方法呢?那我們來看看:
public static void main(String[] args) {
String str = "hello";
String china = "方世享";
System.out.println(str.charAt(1)); //e
System.out.println(china.charAt(1)); //世
}
顯然,這個(gè)對上層的調(diào)用者是完全透明的,完全是底層的數(shù)據(jù)結(jié)構(gòu)存儲而已。但是有必要對比一下源碼,還是有非常大的區(qū)別的:
java8的charAt方法源碼: 實(shí)現(xiàn)起來簡單很多吧
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
java9的charAt方法源碼:
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
7、集合工廠方法:快速創(chuàng)建只讀集合
為了保證數(shù)據(jù)的安全性,有時(shí)候我們需要創(chuàng)建一個(gè)只讀的List。在JDK8的時(shí)候,我們只能這么做:
Collections.unmodifiableList(list)
Collections.unmodifiableSet(set)
Collections.unmodifiableMap(map)
Tips:Arrays.asList(1,2,3)創(chuàng)建的List也是只讀的,不能添加刪除,但是一般我們并不會把他當(dāng)作只讀來用。
可以說是比較繁瑣的一件事。Java 9 因此引入了方便的方法,這使得類似的事情更容易表達(dá)。調(diào)用集合中靜態(tài)方法 of(),可以將不同數(shù)量的參數(shù)傳輸?shù)酱斯S方法。此功能可用于 Set 和 List,也可用于 Map 的類似形式。此時(shí)得到
的集合,是不可變的:
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
//Map的兩種初始化方式,個(gè)人喜歡第二種,語意更加清晰些,也不容易錯(cuò)
Map<String, Integer> map1 = Map.of("Tom", 12, "Jerry", 21,
"Lilei", 33, "HanMeimei", 18);
Map<String, Integer> map2 = Map.ofEntries(
Map.entry("Tom", 89),
Map.entry("Jim", 78),
Map.entry("Tim", 98)
);
處于好奇心,可以讓大家再對比一下類型,看看怎么實(shí)現(xiàn)的:
public static void main(String[] args) {
List<String> list = List.of("a", "b", "c");
List<String> listOld = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));
System.out.println(list.getClass().getName()); //java.util.ImmutableCollections$ListN
System.out.println(listOld.getClass().getName()); //java.util.Collections$UnmodifiableRandomAccessList
}
8、增強(qiáng)的 Stream API
在 Java 9 中,Stream API 變得更好,Stream 接口中添加了 4 個(gè)新的方法:dropWhile, takeWhile, ofNullable,還有個(gè) iterate 方法的新重載方法,可以讓你提供一個(gè) Predicate (判斷條件)來指定什么時(shí)候結(jié)束迭代。
除了對 Stream 本身的擴(kuò)展,Optional 和 Stream 之間的結(jié)合也
得到了改進(jìn)。現(xiàn)在可以通過 Optional 的新方法 stream() 將一個(gè)
Optional 對象轉(zhuǎn)換為一個(gè)(可能是空的) Stream 對象
- takeWhile():返回從開頭開始的盡量多的元素
- dropWhile() :行為與 takeWhile 相反,返回剩余的元素
- ofNullable():Stream 不能全為 null,否則會報(bào)空指針異常。而 Java 9 中的ofNullable健壯性就比of強(qiáng)很多??梢园粋€(gè)非空元素,也可以創(chuàng)建一個(gè)空 Stream
//報(bào) NullPointerException 因?yàn)镺f方法不允許全為null的
//Stream<Object> stream1 = Stream.of(null);
//System.out.println(stream1.count());
//ofNullable():允許值為 null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());//0
- iterator()重載方法。如下,相當(dāng)于不僅僅是limit,而是可以寫邏輯來判斷終止與否了

9、全新的 HTTP 客戶端 API
HTTP,用于傳輸網(wǎng)頁的協(xié)議,早在 1997 年就被采用在目前的 1.1
版本中。直到 2015 年,HTTP2 才成為標(biāo)準(zhǔn)。
Java 9 中有新的方式來處理 HTTP 調(diào)用。它提供了一個(gè)新的 HTTP客戶端( HttpClient ), 它 將 替代僅適用于 blocking 模式的HttpURLConnection (HttpURLConnection是在HTTP 1.0的時(shí)代創(chuàng)建的,并使用了協(xié)議無關(guān)的方法),并提供對 WebSocket 和 HTTP/2 的支持。
此外,HTTP 客戶端還提供 API 來處理 HTTP/2 的特性,比如流和
服務(wù)器推送等功能。全新的 HTTP 客戶端 API 可以從 jdk.incubator.httpclient 模塊中獲取。因?yàn)樵谀J(rèn)情況下,這個(gè)模塊是不能根據(jù) classpath 獲取的,需要使用 add modules 命令選項(xiàng)配置這個(gè)模塊,將這個(gè)模塊添加到 classpath中。
栗子:
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).GET().build();
HttpResponse<String> response = client.send(req,
HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.version().name());
System.out.println(response.body());
10、其它特性
Deprecated 廢棄了相關(guān) API
Java 9 廢棄或者移除了幾個(gè)不常用的功能。其中最主要的是
Applet API,現(xiàn)在是標(biāo)記為廢棄的。隨著對安全要求的提高,主流瀏
覽器已經(jīng)取消對 Java 瀏覽器插件的支持
智能 Java 編譯工具
智能 java 編譯工具( sjavac )的第一個(gè)階段始于 JEP139 這個(gè)項(xiàng)目,用于在多核處理器情況下提升 JDK 的編譯速度
JDK 9 還更新了 javac 編譯器以便能夠?qū)?java 9 代碼編譯運(yùn)行在低版本 Java 中
統(tǒng)一的 JVM 日志系統(tǒng)
javadoc 的 HTML 5 支持
Nashorn 項(xiàng)目在 JDK 9 中得到改進(jìn),它為 Java 提供輕量級的Javascript 運(yùn)行時(shí)。
JDK 9 包含一個(gè)用來解析 Nashorn 的 ECMAScript 語法樹的API。這個(gè) API 使得 IDE 和服務(wù)端框架不需要依賴 Nashorn 項(xiàng)目的內(nèi)部實(shí)現(xiàn)類,就能夠分析 ECMAScript 代碼。
Javascript 引擎升級:Nashorn(該引擎在8中首次引入,非常好用)
java 的動態(tài)編譯器
**JIT(Just-in-time)**編譯器可以在運(yùn)行時(shí)將熱點(diǎn)編譯成本地代碼,
速度很快。但是 Java 項(xiàng)目現(xiàn)在變得很大很復(fù)雜,因此 JIT 編譯器需
要花費(fèi)較長時(shí)間才能熱身完,而且有些 Java 方法還沒法編譯,性能
方面也會下降。AoT 編譯就是為了解決這些問題而生的
JIT是個(gè)很大的研究課題,阿里有專門的團(tuán)隊(duì)搞這一塊
最后:
Java9有一個(gè)重大的變化,就是垃回收器默認(rèn)采用了G1。
Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合,同時(shí)把G1設(shè)為默認(rèn)的垃圾回收器實(shí)現(xiàn)。替代了之前默認(rèn)使用的Parallel GC,對于這個(gè)改變,evens的評論是醬紫的:這項(xiàng)變更是很重要的,因?yàn)橄鄬τ赑arallel來說,G1會在應(yīng)用線程上做更多的事情,而Parallel幾乎沒有在應(yīng)用線程上做任何事情,它基本上完全依賴GC線程完成所有的內(nèi)存管理。這意味著切換到G1將會為應(yīng)用線程帶來額外的工作,從而直接影響到應(yīng)用的性能
CMS收集器與G1收集器的區(qū)別,參考:CMS收集器與G1收集器
|