體驗 Java 9(2):更新與非重大改動
本文原發於我的個人部落格:https://hltj.me/java/2018/04/02/experience-java9-update-minors.html。本副本只用於圖靈社群,禁止第三方轉載。
本篇介紹 Java 9 更新以及一些非重大改動。
更新
Java 9 已經正式釋出半年多了。這期間不僅 Java 9 釋出了更新,就連 Java 10 也已正式釋出。上一篇中提到的工具也都有更新。其中 IDEA 新版改進了不少 Java 9 支援,Eclipse 新版內建了 Java 9 支援(不再需要 Beta 版外掛,但可能需要重灌,不能直接從舊版升級)。最值得一提的是 lombok 1.16.20 釋出。
Lombok 相容性改善
期待已久的 lombok 1.16.20 終於在 2018-01-09 正式釋出。這一版解決了上一篇中提到的大多數相容問題。具體如下: 1. 可以與 Gradle 4.1 及以上版本配合使用。 這點很重要,因為 Gradle 4.2 及以上版本才能支援 Java 9.0.1/9.0.4。 2. 改進 IDE 支援。 將上一篇中提到的 Lombok 示例 中的 Lombok 版本升級到 1.16.20 後,在新版 IDEA 中無論建立普通專案、Maven 專案還是 Gradle 專案,都能正常編輯與執行;在新版 Eclipse 中,Maven、Gradle 專案正常,普通專案還是有問題;在新版 NetBeans 開發版中普通專案、Maven 專案均正常,Gradle 專案在 JDK 9 環境正常,JDK 9.0.1/9.0.4 環境有問題。
結合一篇的內容,彙總 Lombok 1.16.18 到 1.16.20 相容情況變化如下表所示:
表中箭頭(⇨)之前表示 Lombok 1.16.18 對工具的相容情況,箭頭之後表示 Lombok 1.16.20 的相容情況。
Java 9 更新與版本號模式
Java 9 的安全更新版 9.0.1、9.0.4 分別於 2017-10-17、2018-01-16 釋出。可以看出這裡的 Java 版本號與以往 1.8.0_162
這種版本號差異很明顯。Java 9 的版本號模式(version schema)為:
$MAJOR.$MINOR.$SECURITY
即“主版本號.次版本號.安全級別”,這樣很容易看出 9.0.1 與 9.0.4 都是安全修復版本,應該升級。
Java 9 還增加了用於獲取版本號的 API,以前取 Java 版本號主要通過系統屬性來取,現在可以直接呼叫 API 了。java.lang.Runtime
新添了一個靜態方法 version()
,其返回值型別為 Runtime.Version
,通過它可以很方便地獲取不同格式的版本號。例如:
jshell> Runtime.version()
$1 ==> 9.0.4+11
jshell> Runtime.version().version()
$2 ==> [9, 0, 4]
jshell> Runtime.version().major()
$3 ==> 9
jshell> Runtime.version().security()
$4 ==> 4
jshell> Runtime.version().build()
$5 ==> Optional[11]
值得補充的是,Java 10 再次修改版本號模式為:
$FEATURE.$INTERIM.$UPDATE.$EMERG
即“特性版本號.中間版本號.更新版本號.緊急修復版本號”。 同時 Runtime.Version
的相應方法 major()
、minor()
、security()
也被棄用,建議分別使用新增的 feature()
、interim()
、update()
方法取代,另外新增 patch()
方法用於取緊急修復版本號。例如:
jshell> Runtime.version()
$1 ==> 10+46
jshell> Runtime.version().feature()
$2 ==> 10
jshell> Runtime.version().build()
$3 ==> Optional[46]
當然 Java 10 與 Java 9 的前三位版本號的含義並不完全相同。特別是自 Java 10 起將嚴格按照 6 個月的節奏發版,即今年三月份釋出 Java 10、九月份釋出 Java 11、明年三月份釋出 Java 12……以此類推。
關於 Java 9 與 Java 10 版本號模式的更多內容請參見 JEP 223: New Version-String Scheme 以及 JEP 322: Time-Based Release Versioning。
非重大改動
接下來介紹以下內容:
- 下劃線成為保留字
- try-with-resource 改進
- InputStream 改進
- 介面的私有預設方法
- 集合的工廠方法
- Optional 改進
- Stream 改進
- 程式 API 改進
如果已經瞭解過就不需要再往下看了。
下劃線成為保留字
單獨一個下劃線(_
)作為識別符號在 Java 8 中就已經棄用,但是仍然可以通過編譯。例如下述程式碼:
public class Demo0 {
public static void main(String[] args) {
int _ = 1;
System.out.println(_);
}
}
在 Java 8 環境中編譯會出現以下警告:
$ javac Demo0.java
Demo0.java:3: warning: '_' used as an identifier
int _ = 1;
^
(use of '_' as an identifier might not be supported in releases after Java SE 8)
Demo0.java:4: warning: '_' used as an identifier
System.out.println(_);
^
(use of '_' as an identifier might not be supported in releases after Java SE 8)
2 warnings
$ java Demo0
1
而在 Java 9 環境中則不能通過編譯:
$ javac Demo0.java
Demo0.java:3: error: as of release 9, '_' is a keyword, and may not be used as an identifier
int _ = 1;
^
Demo0.java:4: error: as of release 9, '_' is a keyword, and may not be used as an identifier
System.out.println(_);
^
2 errors
在 JShell 中使用也會出現同樣的報錯:
jshell> String _ = "foo";
| Error:
| as of release 9, '_' is a keyword, and may not be used as an identifier
| String _ = "foo";
| ^
這是因為 _
已經成為保留字,在未來的 Java 版本中有可能用作佔位符,類似 Scala 的模式匹配語法或者 Kotlin 的解構語法中的佔位符。
try-with-resource 改進
Java 7 引入了 try-with-resource 語法,對於支援自動清理的資源(實現了 AutoCloseable
介面的型別),將引用建立的語句放在 try 與左花括號之間圓括號中,就可以執行自動清理。例如
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
對於既有引用,在 Java 7/8 中需要引入臨時變數,例如:
BufferedReader br = new BufferedReader(new FileReader(path));
try (BufferedReader br1 = br) {
return br1.readLine();
}
而在 Java 9 中,使用既有 final(或相當於 final)的可自動清理資源引用時無需引入臨時變數。上述程式碼可以簡化為:
BufferedReader br = new BufferedReader(new FileReader(path));
try (br) {
return br.readLine();
}
InputStream
改進
Java 9 的 InputStream
新添了一個 transferTo()
方法,可以從輸入流讀取並寫到輸出流。例如,在 JShell 中用一行程式碼實現終端輸入回顯(按 Ctrl-C 結束):
jshell> System.in.transferTo(System.out)
Hello
Hello
0123456789
0123456789
$1 ==>
有了這個函式,實現類似 IOUtils.toString()
的邏輯就很方便了:
jshell> String inputToString(InputStream is) throws IOException {
...> ByteArrayOutputStream os = new ByteArrayOutputStream();
...> is.transferTo(os);
...> return os.toString();
...> }
| created method inputToString(InputStream)
jshell> inputToString(new ByteArrayInputStream("Hello".getBytes()))
$2 ==> "Hello"
介面的私有預設方法
Java 8 引入了介面的預設方法,可以為介面提供預設實現。例如我們有一個介面 Person
以及很多實現該介面的類,當我們為 Person
介面新增 greeting()
、farewell()
兩個方法時,可以為其提供預設實現,而無需每個實現 Person
介面的類都實現一遍:
public interface Person {
// 其他介面 ……
String getName();
default void greeting() {
System.out.println(getName() + ": Hello!");
}
default void farewell() {
System.out.println(getName() + ": Goodbye!");
}
}
Java 9 的介面支援私有預設方法,可以讓多個預設方法複用程式碼:
public interface Person {
// 其他介面 ……
String getName();
default void greeting() {
say("Hello!");
}
default void farewell() {
say("Goodbye!");
}
private void say(String words) {
System.out.println(getName() + ": " + words);
}
}
假如這裡的私有預設方法 say()
並沒有呼叫例項方法 getName()
,那麼還可以使用靜態私有方法:
public interface Person {
// 其他介面 ……
default void greeting() {
say("Hello!");
}
default void farewell() {
say("Goodbye!");
}
private static void say(String words) {
System.out.println(words);
}
}
集合的工廠方法
在 Java 9 之前,建立帶有初始字面值的只讀 List、Set 與 Map 的程式碼語法風格各異,甚至有些繁瑣麻煩。例如:
// import static java.util.Collections.unmodifiableSet;
// import static java.util.Collections.unmodifiableMap;
// import static java.util.stream.Collectors.collectingAndThen;
// import static java.util.stream.Collectors.toSet
// import static java.util.stream.Collectors.toMap
// import static java.util.AbstractMap.SimpleEntry;
final List<String> list = Arrays.asList("foo", "bar", "baz");
final Set<Integer> numbers = unmodifiableSet(new HashSet<Integer>() {
{
add(1); add(2); add(3);
}
});
// 或者
final Set<Integer> numbers1 =
unmodifiableSet(new HashSet<Integer>(Arrays.asList(1, 2, 3)));
// 或者(Java 8)
final Set<Integer> numbers2 =
Stream.of(1, 2, 3)
.collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
final Map<Integer, String> map =
unmodifiableMap(new HashMap<Integer, String>() {
{
put(1, "foo");
put(2, "bar");
put(3, "baz");
}
});
// 或者(Java 8)
final Map<Integer, String> map1 =
unmodifiableMap(Stream.of(
new SimpleEntry<>(1, "foo"),
new SimpleEntry<>(2, "bar"),
new SimpleEntry<>(3, "baz"))
.collect(toMap((e) -> e.getKey(), (e) -> e.getValue()))
);
而在 Java 9 中可以使用更簡潔、更直觀、更統一的方式:
// import static java.util.Map.entry
final List<String> list = List.of("foo", "bar", "baz");
final Set<Integer> numbers = Set.of(1, 2, 3);
final Map<Integer, String> map = Map.of(1, "foo", 2, "bar", 3, "barz");
// 或者
final Map<Integer, String> map1 = Map.ofEntries(
entry(1, "foo"),
entry(2, "bar"),
entry(3, "baz")
);
Optional 改進
新增 or
方法
Optional
類新增 or()
方法,它與 orElseGet()
類似,只是其引數 lambda 的返回值以及自身的返回值都是 Optional
,可以用於從一系列可選值中選取第一個有值的。例如:
jshell> import static java.util.Optional.*;
jshell> empty().or(() -> empty()).or(() -> empty())
$2 ==> Optional.empty
jshell> empty().or(() -> empty()).or(() -> of("Hello")).or(() -> empty())
$3 ==> Optional[Hello]
jshell> empty().or(() -> of("Wolrd")).or(() -> of("Hello")).or(() -> empty())
$4 ==> Optional[Wolrd]
注:只有
Optional
類新添了這個方法,而OptionalInt
等可選類並未新增。
新增 ifPresentOrElse
方法
在 Java 8 中可選類都實現了 ifPresent
,可以很容易實現有值時輸出其內容:
jshell> OptionalDouble.of(Math.PI).ifPresent(System.out::println)
3.141592653589793
但如果要同時實現無值時輸出預設值,它就無能為力了。這時 Java 9 的 ifPresentOrElse
就派上用場了:
jshell> OptionalDouble.of(Math.PI).ifPresentOrElse(System.out::println, () -> System.out.println("No Value"))
3.141592653589793
jshell> OptionalDouble.empty().ifPresentOrElse(System.out::println, () -> System.out.println("No Value"))
No Value
新增 stream
方法
在 Java 8 中如果想對 OptionalInt
等基本型別可選值做流式集合處理會非常麻煩:
jshell> OptionalLong optlong = OptionalLong.of(1L)
optlong ==> OptionalLong[1]
jshell> (optlong.isPresent()? LongStream.of(optlong.getAsLong()): LongStream.empty()).map(x ->x + 1).boxed().collect(Collectors.toList())
$2 ==> [2]
Java 9 新增了 stream()
方法可使其簡化很多:
jshell> optlong.stream().map(x ->x + 1).boxed().collect(Collectors.toList())
$3 ==> [2]
Stream 類改進
新增 ofNullable
工廠方法
在 Java 9 中如需對可空物件進行流式集合處理,可以藉助 Optional
來實現:
jshell> Optional.ofNullable("hello").stream().count()
$1 ==> 1
jshell> Optional.ofNullable(null).stream().count()
$2 ==> 0
另外,Java 9 也為 Stream
類新增了 ofNullable()
方法,上述程式碼還可以簡化為:
jshell> Stream.ofNullable("hello").count()
$3 ==> 1
jshell> Stream.ofNullable(null).count()
$4 ==> 0
注:只有
Stream
類新添了這個方法,而IntStream
等並未新增。
新增 takeWhile
與 dropWhile
方法
分別用於“取元素直到指定值出現”、“直到指定值出現開始取元素”,例如:
jshell> import static java.util.stream.Collectors.toList;
jshell> IntStream.of(9, 6, 3, 0, 2, 4, 6).takeWhile(n -> n != 0).boxed().collect(toList())
$2 ==> [9, 6, 3]
jshell> IntStream.of(9, 6, 3, 0, 2, 4, 6).dropWhile(n -> n != 0).boxed().collect(toList())
$3 ==> [0, 2, 4, 6]
iterate
方法新增過載
使用 Java 8 的 iterate
以及上述 takeWhile
實現輸出 10 以內的偶數如下:
jshell> IntStream.iterate(0, i -> i + 2).takeWhile(i -> i <= 10).forEach(System.out::println)
0
2
4
6
8
10
不過 Java 9 還為 iterate
新添了支援判斷迭代條件的過載形式,因此上述程式碼可改為:
jshell> IntStream.iterate(0, i -> i <= 10, i -> i + 2).forEach(System.out::println)
0
2
4
6
8
10
程式 API 改進
Java 9 新增了 ProcessHandle
介面,可以很方便地獲得程式的 pid 與程式資訊。ProcessHandle
還提供了兩個靜態方法,分別用於獲取當前程式與指定 pid 的 ProcessHandle
例項。例如:
jshell> ProcessHandle.current().pid()
$1 ==> 27137
jshell> ProcessHandle.current().parent()
$2 ==> Optional[27112]
jshell> ProcessHandle.of(27112).get().children().count()
$3 ==> 1
jshell> ProcessHandle.current().info().command()
$4 ==> Optional[/usr/local/jdk-9.0.4/bin/java]
jshell> ProcessHandle.current().info().totalCpuDuration()
$5 ==> Optional[PT0.81S]
關於這些新特性的更多細節請參見官方文件。
本系列未完待續。
相關文章
- Java實驗2 方法與陣列Java陣列
- Java9系列第九篇-對HTTP2協議的支援與非阻塞HTTP-APIJavaHTTP協議API
- 【VMware vSphere】VMware vSphere 9 將有 vLCM 重大改進?
- 軟體過程與管理實驗2
- 基於NACOS和JAVA反射機制動態更新JAVA靜態常量非@Value註解Java反射
- Java堆記憶體Heap與非堆記憶體Non-HeapJava記憶體
- 這個簡單的竅門能大大改善 React 開發體驗React
- Java中"與"、"或"、"非"、"異或"Java
- ViewPager2重大更新,支援offscreenPageLimitViewpagerMIT
- WSL 2體驗
- 元件引數校驗與非props特性元件
- OS課 Level 2 實驗(2):軟體的部署與應用
- Struts框架_9 Struts2的驗證框架
- Java on Visual Studio Code 2月更新Java
- .NET9 - 新功能體驗(一)
- .NET9 - 新功能體驗(二)
- .NET9 - 新功能體驗(三)
- html初體驗#2HTML
- Final Cut Pro X已更新,工作流程有了重大改進
- 《三國殺》聯手非遺重現三國文化 遊卡文化IP體驗新升級
- java B2B2C Springcloud電子商城系統- Gateway初體驗JavaSpringGCCloudGateway
- 面向體驗的重構優化優化
- 實驗2 類與物件物件
- win10更新自動重啟怎麼關掉_如何徹底關掉win10更新自動重啟Win10
- java9+springboot2+undertow2啟用http2及server pushJavaSpring BootHTTPServer
- 【linux】驅動-13-阻塞與非阻塞Linux
- 萌新的裝機體驗(持續更新)
- Java動態編譯和熱更新Java編譯
- vue2+vite初體驗VueVite
- 在nodejs中體驗http/2NodeJSHTTP
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- 實驗2-2-9 計算火車執行時間 (15分)
- java TreeSet去重與排序入門Java排序
- Java 重寫(Override)與過載(Overload)JavaIDE
- 對稱與非對稱密碼體制密碼
- golang 與 docker 初體驗GolangDocker
- 什麼?Java9這些史詩級更新你都不知道?Java9特性一文打盡!Java
- Azure Kubernates Service 更新|提升開發體驗和效率