經過4次跳票,歷經曲折的java 9 終于終于在2017年9月21日發布(距離上個版本足足3年半時間)
java 9 提供了超過 150 項新功能特性,包括備受期待的模塊化系統、可交互的 REPL 工具:jshell,JDK 編譯工具,Java 公共 API 和私有代碼,以及安全增強、擴展提升、性能管理改善等。可以說 Java 9 是一個龐大的系統工程,完全做了一個整體改變。但本博文只介紹最重要的十大新特性
它的新特性來自于100于項JEP和40于項JSR
模塊化系統Java7開始籌備,Java8進行了大量工作,Java9才落地。首先帶來最直觀的感受,就是目錄結構的感受:
JDK8以及以前版本:
在模塊的 src 下創建 module-info.java 文件,來描述依賴和導出(暴露)。
requires:指明對其它模塊的依賴。
exports:控制著哪些包可以被其它模塊訪問到。所有不被導出的包
默認都被封裝在模塊里面。
REPL:read - evaluate - print - loop
這個簡單的說就是能想腳本語言那樣,所見即所得。之前我們用java,哪怕只想輸出一句hello world,都是非常麻煩的。需要建文件、寫代碼、編譯、運行等等。現在有了jShell工具,實在太方便了
當一個新版本的 Java 出現的時候,你的庫用戶要花費數年時間才會
切換到這個新的版本。這就意味著庫得去向后兼容你想要支持的最老
的 Java 版本(許多情況下就是 Java 6 或者 Java7)。這實際上意味著
未來的很長一段時間,你都不能在庫中運用 Java 9 所提供的新特性。
幸運的是,多版本兼容 jar 功能能讓你創建僅在特定版本的 Java 環境
中運行庫程序選擇使用的 class 版本
案例:略
在 Java 9 中,接口更加的靈活和強大,連方法的訪問權限修飾符
都可以聲明為 private 的了,此時方法將不會成為你對外暴露的 API
的一部分(個人認為,這肯定是JDK8遺漏了的一個點,哈哈)
看個例子:
public static String staticFun() { privateFun(); return ""; } default String defaultFun() { privateFun(); return ""; } private static void privateFun() { System.out.println("我是私有方法~"); }
這樣子是沒有問題,可以正常調用和使用的。但是需要注意一下兩點
default String defaultFun() { privateFun(); return ""; } private void privateFun() { System.out.println("我是私有方法~"); }
在 java 8 中如下的操作是會報錯的:
public static void main(String[] args) { Set<String> set1 = new HashSet<>(); //最常用的初始化 //Set<String> set2 = new HashSet<>(){}; //在JDK8中報錯 Set<String> set2 = new HashSet<String>(){}; //這樣在JDK8中也正常 Set<String> set3 = new HashSet<String>(){{}}; //這樣也都是正常的 }
由此課件,報錯的那種情況是因為在JDK8中,還不能直接推斷出鉆石操作符里面的類型而報錯。而我們在JDK9以后,就可以直接這么寫了:
public static void main(String[] args) { Set<String> set1 = new HashSet<>(); //最常用的初始化 Set<String> set2 = new HashSet<>(){}; //在JDK8中報錯 Set<String> set3 = new HashSet<>(){{}}; //這樣也都是正常的 }
這樣寫都是不會報錯,可以直接書寫使用的。相當于直接創建了一個HashMap的子類。
這個點非常的小。距離說明就懂了
在Java8中,我們給變量取名直接就是_
public static void main(String[] args) { String _ = "hello"; System.out.println(_); //hello }
UTF-8表示一個字符是個動態的過程,可以能用1、2、3個字節都是有可能的。但是UTF-16明確的就是不管你是拉丁文、中文等,都是恒定的用兩個字節表示
JDK8的字符串存儲在char類型的數組里面,不難想象在絕大多數情況下,char類型只需要一個字節就能表示出來了,比如各種字母什么的,兩個字節存儲勢必會浪費空間,JDK9的一個優化就在這,內存的優化。
Java8:
private final char value[];
Java9:
private final byte[] value;
結論:String 再也不用 char[] 來存儲啦,改成了 byte[] 加上編碼標
記,節約了不少空間。由于底層用了字節數組byte[]來存儲,所以遇上非拉丁文,JDK9配合了一個encodingFlag來配合編碼解碼的
so,相應的StringBuffer 和 StringBuilder 也對應的做出了對應的變化。
有的人擔心,這會不會影響到我的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)); //世 }
顯然,這個對上層的調用者是完全透明的,完全是底層的數據結構存儲而已。但是有必要對比一下源碼,還是有非常大的區別的:
java8的charAt方法源碼: 實現起來簡單很多吧
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); } }
為了保證數據的安全性,有時候我們需要創建一個只讀的List。在JDK8的時候,我們只能這么做:
Collections.unmodifiableList(list)Collections.unmodifiableSet(set)Collections.unmodifiableMap(map)
Tips:Arrays.asList(1,2,3)創建的List也是只讀的,不能添加刪除,但是一般我們并不會把他當作只讀來用。
可以說是比較繁瑣的一件事。Java 9 因此引入了方便的方法,這使得類似的事情更容易表達。調用集合中靜態方法 of(),可以將不同數量的參數傳輸到此工廠方法。此功能可用于 Set 和 List,也可用于 Map 的類似形式。此時得到
的集合,是不可變的:
List<String> list = List.of("a", "b", "c"); Set<String> set = Set.of("a", "b", "c"); //Map的兩種初始化方式,個人喜歡第二種,語意更加清晰些,也不容易錯 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) );
處于好奇心,可以讓大家再對比一下類型,看看怎么實現的:
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 }
在 Java 9 中,Stream API 變得更好,Stream 接口中添加了 4 個新的方法:dropWhile, takeWhile, ofNullable,還有個 iterate 方法的新重載方法,可以讓你提供一個 Predicate (判斷條件)來指定什么時候結束迭代。
除了對 Stream 本身的擴展,Optional 和 Stream 之間的結合也
得到了改進。現在可以通過 Optional 的新方法 stream() 將一個
Optional 對象轉換為一個(可能是空的) Stream 對象
//報 NullPointerException 因為Of方法不允許全為null的//Stream<Object> stream1 = Stream.of(null);//System.out.println(stream1.count());//ofNullable():允許值為 nullStream<Object> stream1 = Stream.ofNullable(null);System.out.println(stream1.count());//0
HTTP,用于傳輸網頁的協議,早在 1997 年就被采用在目前的 1.1
版本中。直到 2015 年,HTTP2 才成為標準。
Java 9 中有新的方式來處理 HTTP 調用。它提供了一個新的 HTTP客戶端( HttpClient ), 它 將 替代僅適用于 blocking 模式的HttpURLConnection (HttpURLConnection是在HTTP 1.0的時代創建的,并使用了協議無關的方法),并提供對 WebSocket 和 HTTP/2 的支持。
此外,HTTP 客戶端還提供 API 來處理 HTTP/2 的特性,比如流和
服務器推送等功能。全新的 HTTP 客戶端 API 可以從 jdk.incubator.httpclient 模塊中獲取。因為在默認情況下,這個模塊是不能根據 classpath 獲取的,需要使用 add modules 命令選項配置這個模塊,將這個模塊添加到 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());
Java 9 廢棄或者移除了幾個不常用的功能。其中最主要的是
Applet API,現在是標記為廢棄的。隨著對安全要求的提高,主流瀏
覽器已經取消對 Java 瀏覽器插件的支持
智能 java 編譯工具( sjavac )的第一個階段始于 JEP139 這個項目,用于在多核處理器情況下提升 JDK 的編譯速度
JDK 9 還更新了 javac 編譯器以便能夠將 java 9 代碼編譯運行在低版本 Java 中
Nashorn 項目在 JDK 9 中得到改進,它為 Java 提供輕量級的Javascript 運行時。
JDK 9 包含一個用來解析 Nashorn 的 ECMAScript 語法樹的API。這個 API 使得 IDE 和服務端框架不需要依賴 Nashorn 項目的內部實現類,就能夠分析 ECMAScript 代碼。
**JIT(Just-in-time)**編譯器可以在運行時將熱點編譯成本地代碼,
速度很快。但是 Java 項目現在變得很大很復雜,因此 JIT 編譯器需
要花費較長時間才能熱身完,而且有些 Java 方法還沒法編譯,性能
方面也會下降。AoT 編譯就是為了解決這些問題而生的
JIT是個很大的研究課題,阿里有專門的團隊搞這一塊
Java9有一個重大的變化,就是垃回收器默認采用了G1。
Java 9 移除了在 Java 8 中 被廢棄的垃圾回收器配置組合,同時把G1設為默認的垃圾回收器實現。替代了之前默認使用的Parallel GC,對于這個改變,evens的評論是醬紫的:這項變更是很重要的,因為相對于Parallel來說,G1會在應用線程上做更多的事情,而Parallel幾乎沒有在應用線程上做任何事情,它基本上完全依賴GC線程完成所有的內存管理。這意味著切換到G1將會為應用線程帶來額外的工作,從而直接影響到應用的性能
CMS收集器與G1收集器的區別,參考:CMS收集器與G1收集器