
本文收錄在 《從小工到專家的 Java 進階之旅》 系列專欄中,
你好,我是看山,
從 2017 年開始,Java 版本更新策略從原來的每兩年一個新版本,改為每六個月一個新版本,以快速驗證新特性,推動 Java 的發展,從 《JVM Ecosystem Report 2021》 中可以看出,目前開發環境中有近半的環境使用 Java8,有近半的人轉移到了 Java11,隨著 Java17 的發布,相信比例會有所變化,
因此,準備出一個系列,配合示例講解,闡述各個版本的新特性,
概述
本文講解一下 Java12 的特性,作為第一個長期支持版 Java11 之后的第一個版本,增加的功能也不少,除了一些小幅度的 API 增強,增加了另一個試驗階段的垃圾收集器 Shenandoah、對 G1 做了優化、增加微基準套件等,
語法特性
Java12 提供了很多的語法特性,既有小而美的增強 API,又有特別方便的工具擴展,本節我們跟著代碼看看比較好玩的功能,
String 的增強方法:indent 和 transform
在 Java12 中,String 又增強了兩個方法,之所以說又,是因為在 Java11 中已經增加過小而美的方法,想要詳細了解的可以查看 Java11 新特性,
這次增加的方法是indent(縮進)和transform(轉換),
顧名思義,indent方法是對字串每行(使用\r或\n分隔)資料縮進指定空白字符,引數是 int 型別,
如果引數大于 0,就縮進指定數量的空格;如果引數小于 0,就將左側的空字符洗掉指定數量,即右移,
我們看下原始碼:
public String indent(int n) {
if (isEmpty()) {
return "";
}
Stream<String> stream = lines();
if (n > 0) {
final String spaces = " ".repeat(n);
stream = stream.map(s -> spaces + s);
} else if (n == Integer.MIN_VALUE) {
stream = stream.map(s -> s.stripLeading());
} else if (n < 0) {
stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
}
return stream.collect(Collectors.joining("\n", "", "\n"));
}
這里會使用到 Java11 增加的lines、repeat、stripLeading等方法,indent最后會將多行資料通過Collectors.joining("\n", "", "\n")方法拼接,結果會有兩點需要注意:
\r會被替換成\n;- 如果原字串是多行資料,最后一行的結尾沒有
\n,最后會補上一個\n,即多了一個空行,
我們看下測驗代碼:
@Test
void testIndent() {
final String text = "\t\t\t 你好,我是看山,\n \u0020\u2005Java12 的 新特性,\r 歡迎三連+關注喲";
assertEquals(" \t\t\t 你好,我是看山,\n \u0020\u2005Java12 的 新特性,\n 歡迎三連+關注喲、n", text.indent(4));
assertEquals("\t 你好,我是看山,\n\u2005Java12 的 新特性,\n 歡迎三連+關注喲、n", text.indent(-2));
final String text2 = "山水有相逢";
assertEquals("山水有相逢", text2);
}
我們再來看看transform方法,原始碼一目了然:
public <R> R transform(Function<? super String, ? extends R> f) {
return f.apply(this);
}
通過傳入的Function對當前字串進行轉換,轉換結果由Function決定,比如,我們要對字串反轉:
@Test
void testTransform() {
final String text = "看山是山";
final String reverseText = text.transform(s -> new StringBuilder(s).reverse().toString());
assertEquals("山是山看", reverseText);
}
其實這個方法在 Java8 中提供的Optional實作類似的功能(完整的 Optional 功能可以查看 Optional 的 6 種操作):
@Test
void testTransform() {
final String text = "看山是山";
final String reverseText2 = Optional.of(text)
.map(s -> new StringBuilder(s).reverse().toString())
.orElse("");
assertEquals("山是山看", reverseText2);
}
Files 的增強方法:mismatch
在 Java12 中,Files增加了mismatch方法,用于對比兩個檔案中的不相同字符的位置,如果內容相同,回傳-1L,是long型別的,
我們來簡單看下怎么用:
@Test
void testMismatch() throws IOException {
final Path pathA = Files.createFile(Paths.get("a.txt"));
final Path pathB = Files.createFile(Paths.get("b.txt"));
// 寫入相同內容
Files.write(pathA, "看山".getBytes(), StandardOpenOption.WRITE);
Files.write(pathB, "看山".getBytes(), StandardOpenOption.WRITE);
final long mismatch1 = Files.mismatch(pathA, pathB);
Assertions.assertEquals(-1L, mismatch1);
// 追加不同內容
Files.write(pathA, "是山".getBytes(), StandardOpenOption.APPEND);
Files.write(pathB, "不是山".getBytes(), StandardOpenOption.APPEND);
final long mismatch2 = Files.mismatch(pathA, pathB);
Assertions.assertEquals(6L, mismatch2);
Files.deleteIfExists(pathA);
Files.deleteIfExists(pathB);
}
我們可以看到,當第一次在兩個檔案中寫入相同內容,執行mismatch方法回傳的是-1L,當第二次追加進去不同的內容后,回傳的是6L,之所以是 6,是因為測驗代碼中使用的字符集是UTF-8,大部分漢子是占用 3 個字符,前兩個字相同,從第三個字開始不同,下標從 0 開始,所以開始位置是 6,
Collectors 的增強方法:teeing
我們看下teeing的定義:
public static <T, R1, R2, R> Collector<T, ?, R> teeing(
Collector<? super T, ?, R1> downstream1,
Collector<? super T, ?, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger
)
這個方法有三個引數,前兩個是Collector物件,用于對輸入資料進行預處理,第三個引數是BiFunction,用于將前兩個處理后的結果作為引數傳入BiFunction中,運算得到結果,
我們來看下例子:
@Test
void testTeeing() {
var result = Stream.of("Sunday", "Monday", "Tuesday", "Wednesday")
.collect(Collectors.teeing(
Collectors.filtering(n -> n.contains("u"), Collectors.toList()),
Collectors.filtering(n -> n.contains("n"), Collectors.toList()),
(list1, list2) -> List.of(list1, list2)
));
assertEquals(2, result.size());
assertTrue(isEqualCollection(List.of("Sunday", "Tuesday"), result.get(0)));
assertTrue(isEqualCollection(List.of("Sunday", "Monday", "Wednesday"), result.get(1)));
}
我們對輸入的幾個字串進行過濾,然后將過濾結果組成一個新的佇列,
新工具:CompactNumberFormat
這個工具比較好玩,可以對數字進行按需格式化,提供了public static NumberFormat getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)方法用于初始化:
- 第一個引數是指定區域,不同區域展示的結果不同,比如中國展示漢字、美國展示英文;
- 第二個引數是指定展示結果的模式,分為
SHORT和LONG,不過對于中文展示,似乎沒啥區別,
我們一起看下例子:
@Test
void testFormat() {
final NumberFormat zhShort = NumberFormat.getCompactNumberInstance(Locale.CHINA, Style.SHORT);
assertEquals("1 萬", zhShort.format(10_000));
assertEquals("1 兆", zhShort.format(1L << 40));
final NumberFormat zhLong = NumberFormat.getCompactNumberInstance(Locale.CHINA, Style.LONG);
assertEquals("1 萬", zhLong.format(10_000));
assertEquals("1 兆", zhLong.format(1L << 40));
final NumberFormat usShort = NumberFormat.getCompactNumberInstance(Locale.US, Style.SHORT);
usShort.setMaximumFractionDigits(2);
assertEquals("10K", usShort.format(10_000));
assertEquals("1.1T", usShort.format(1L << 40));
final NumberFormat usLong = NumberFormat.getCompactNumberInstance(Locale.US, Style.LONG);
usLong.setMaximumFractionDigits(2);
assertEquals("10 thousand", usLong.format(10_000));
assertEquals("1.1 trillion", usLong.format(1L << 40));
}
我們也可以繼續使用NumberFormat中的方法定義,比如示例中保留小數點后 2 位,
Shenandoah:一個低停頓垃圾收集器
Java12 引入了一個實驗階段的垃圾收集器:Shenandoah,作為一個低停頓的垃圾收集器,
Shenandoah 垃圾收集器是 RedHat 在 2014 年宣布進行的垃圾收集器研究專案,其作業原理是通過與 Java 應用執行執行緒同時運行來降低停頓時間,簡單的說就是,Shenandoah 作業時與應用程式執行緒并發,通過交換 CPU 并發周期和空間以改善停頓時間,使得垃圾回收器執行執行緒能夠在 Java 執行緒運行時進行堆壓縮,并且標記和整理能夠同時進行,因此避免了在大多數 JVM 垃圾收集器中所遇到的問題,

Shenandoah 垃圾回收器的暫停時間與堆大小無關,這意味著無論將堆設定為 200MB 還是 200GB,都將擁有一致的系統暫停時間,不過實際使用性能將取決于實際作業堆的大小和作業負載,
Java12 中 Shenandoah 處于實驗階段,想要使用需要編譯時添加--with-jvm-features=shenandoahgc,然后啟動時使用-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC以開啟,
后續會補充 Java 中各種垃圾收集器的文章,其中會有介紹 Shenandoah 的,敬請關注公眾號「看山的小屋」,如果想要提前了解,歡迎訪問https://wiki.openjdk.java.net/display/shenandoah,
增加一套基準測驗套件
Java12 中添加一套基準測驗套件,該基準測驗套件基于 JMH(Java Microbenchmark Harness),使開發人員可以輕松運行現有的基準測驗并創建新的基準測驗,其目標是提供一個穩定且優化的基準,
在這套基準測驗套件中包括將近 100 個基準測驗的初始集合,并且能夠輕松添加新基準、更新基準測驗和提高查找已有基準測驗的便利性,
微基準套件與 JDK 源代碼位于同一個目錄中,并且在構建后將生成單個 Jar 檔案,它是一個單獨的專案,在支持構建期間不會執行,以方便開發人員和其他對構建微基準套件不感興趣的人在構建時花費比較少的構建時間,
Switch 運算式擴展(預覽版)
Switch 陳述句出現的姿勢是條件判斷、流程控制組件,與現在很流行的新語言對比,其寫法顯得非常笨拙,所以 Java 推出了 Switch 運算式語法,可以讓我們寫出更加簡化的代碼,這個擴展在 Java12 中作為預覽版首次引入,需要在編譯時增加-enable-preview開啟,在 Java14 中正式提供,功能編號是 JEP 361,
比如,我們通過 switch 語法簡單計算作業日、休息日,在 Java12 之前需要這樣寫:
@Test
void testSwitch() {
final DayOfWeek day = DayOfWeek.from(LocalDate.now());
String typeOfDay = "";
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
typeOfDay = "Working Day";
break;
case SATURDAY:
case SUNDAY:
typeOfDay = "Rest Day";
break;
}
Assertions.assertFalse(typeOfDay.isEmpty());
}
在 Java12 中的 Switch 運算式中,我們可以直接簡化:
@Test
void testSwitchExpression() {
final DayOfWeek day = DayOfWeek.SATURDAY;
final String typeOfDay = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Working Day";
case SATURDAY, SUNDAY -> "Day Off";
};
Assertions.assertEquals("Day Off", typeOfDay);
}
是不是很清爽,文末提供的原始碼中,pom.xml定義的maven.compiler版本寫的是14,這是因為 Switch 運算式是 Java14 正式提供,我沒有重新編譯 Java,所以只能指定 Java14 來實作這個功能代碼的演示,
引入 JVM 常量 API
Java12 中引入 JVM 常量 API,用來更容易地對關鍵類檔案和運行時構件的描述資訊進行建模,特別是對那些從常量池加載的常量,這是一項非常技術性的變化,能夠以更簡單、標準的方式處理可加載常量,
具體來說就是java.base模塊新增了java.lang.constant包,引入了ConstantDesc介面以及Constable介面,ConstantDesc的子介面包括:
ClassDesc:Class 的可加載常量標稱描述符;MethodTypeDesc:方法型別常量標稱描述符;MethodHandleDesc:方法句柄常量標稱描述符;DynamicConstantDesc:動態常量標稱描述符,
繼續挖坑,這部分內容會在進階篇再詳細介紹,敬請關注公眾號「看山的小屋」,
改進 AArch64 實作
Java12 中將只保留一套 AArch64 實作,之前版本中,有兩個關于 aarch64 的實作,分別是ope/src/hotspot/cpu/arm以及open/src/hotspot/cpu/aarch64,它們的實作重復了,為了集中精力更好地實作 aarch64,洗掉了open/src/hotspot/cpu/arm中與 arm64(64-bit Arm platform)實作相關的代碼,只保留 32 位 ARM 埠和 64 位 aarch64 的埠,
這樣做,可以讓開發人員將目標集中在剩下的這個 64 位 ARM 實作上,消除維護兩套埠所需的重復作業,
目標聚焦,力量集中,
默認使用類資料共享(CDS)存檔
在 Java10 的新特性 中我們介紹過類資料共享(CDS,Class Data Sharing),其作用是通過構建時生成默認類串列,在運行時使用記憶體映射,減少 Java 的啟動時間和減少動態記憶體占用量,也能在多個 Java 虛擬機之間共享相同的歸檔檔案,減少運行時的資源占用,
在 Java12 之前,想要使用需要三步走手動開啟,到了 Java12,將默認開啟 CDS 功能,想要關閉,需要使用引數-Xshare:off,
改善 G1 垃圾收集器
能夠中止收集
G1 垃圾收集器可以在大記憶體多處理器的作業場景中提升回收效率,能夠滿足用戶預期降低 STW 停頓時間,
其內部是采用一個高級分析引擎來選擇在收集期間要處理的作業量,此選擇程序的結果是一組稱為 GC 回收集(collection set,CSet)的區域,一旦收集器確定了 GC 回收集 并且 GC 回收、整理作業已經開始,則 G1 收集器必須完成收集集合集的所有區域中的所有活動物件之后才能停止;但是如果收集器選擇過大的 GC 回收集,可能會導致 G1 回收器停頓時間超過預期時間,
在 Java12 中,GC 回收集拆分為必需和可選兩部分,使 G1 垃圾回收器能中止垃圾回收程序,其中必需處理的部分包括 G1 垃圾收集器不能遞增處理的 GC 回收集的部分,同時也可以包含老年代以提高處理效率,在 G1 垃圾回收器完成收集需要必需回收的部分之后,G1 垃圾回收器可以根據剩余時間決定是否停止收集,
向作業系統自動回傳未用堆記憶體
在 Java11 中,G1 僅在進行 Full GC 或并發處理周期時才能向作業系統返還堆記憶體,但是這兩種場景都是 G1 極力避免的,所以如果我們使用 G1 收集器,基本上很難返還 Java 堆記憶體,這樣對于那種周期性執行大量占用記憶體的應用,會造成比較多的記憶體浪費,
Java12 中,G1 垃圾收集器將在應用程式不活動期間定期生成或持續回圈檢查整體 Java 堆使用情況,以便 G1 垃圾收集器能夠更及時的將 Java 堆中不使用記憶體部分返還給作業系統,對于長時間處于空閑狀態的應用程式,此項改進將使 JVM 的記憶體利用率更加高效,
文末總結
本文介紹了 Java12 新增的特性,完整的特性清單可以從https://openjdk.java.net/projects/jdk/12/查看,后續內容會發布在 從小工到專家的 Java 進階之旅 系列專欄中,
青山不改,綠水長流,我們下次見,
推薦閱讀
- 一文掌握 Java8 Stream 中 Collectors 的 24 個操作
- 一文掌握 Java8 的 Optional 的 6 種操作
- 使用 Lambda 運算式實作超強的排序功能
- Java8 的時間庫(1):介紹 Java8 中的時間類及常用 API
- Java8 的時間庫(2):Date 與 LocalDate 或 LocalDateTime 互相轉換
- Java8 的時間庫(3):開始使用 Java8 中的時間類
- Java8 的時間庫(4):檢查日期字串是否合法
- Java8 的新特性
- Java9 的新特性
- Java10 的新特性
- Java11 中基于嵌套關系的訪問控制優化
- Java11 的新特性
- 從小工到專家的 Java 進階之旅
你好,我是看山,游于碼界,戲享人生,如果文章對您有幫助,請點贊、收藏、關注,我還整理了一些精品學習資料,關注公眾號「看山的小屋」,回復“資料”即可獲得,
個人主頁:https://www.howardliu.cn
個人博文:Java 每半年就會更新一次新特性,再不掌握就要落伍了:Java12 的新特性
CSDN 主頁:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就會更新一次新特性,再不掌握就要落伍了:Java12 的新特性
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/413957.html
標籤:其他
