更新時(shí)間:2023-03-21 來源:黑馬程序員 瀏覽量:
JDK 8 已經(jīng)在 2014年 3月 18日正式可用,JDK 8作為長期支持(Long-Term-Support)版本,2018年09月25日作為下一個(gè)LTS的JDK版本:JDK 11也應(yīng)運(yùn)而生,Oracle表示會(huì)對(duì)JDK 11提供大力支持、長期支持。之后陸續(xù)發(fā)布了JDK 12 和JDK 13,JDK14也在2020年 3月17日正式發(fā)布,本節(jié)我們來針對(duì)JDK14的一些顯著新特性做講解,讓大家了解一下JDK 14的一些重要的新特性。
JDK 14一共發(fā)行了16個(gè)JEP(JDK Enhancement Proposals,JDK 增強(qiáng)提案),即是篩選出的JDK 14新特性。
- 343: 打包工具 (Incubator)
- 345: G1的NUMA內(nèi)存分配優(yōu)化
- 349: JFR事件流
- 352: 非原子性的字節(jié)緩沖區(qū)映射
- 358: 友好的空指針異常
- 359: Records (預(yù)覽)
- 361: Switch表達(dá)式 (標(biāo)準(zhǔn))
- 362: 棄用Solaris和SPARC端口
- 363: 移除CMS(Concurrent Mark Sweep)垃圾收集器
- 364: macOS系統(tǒng)上的ZGC
- 365: Windows系統(tǒng)上的ZGC
- 366: 棄用ParallelScavenge + SerialOld GC組合
- 367: 移除Pack200 Tools 和 API
- 368: 文本塊 (第二個(gè)預(yù)覽版)
- 370: 外部存儲(chǔ)器API (Incubator)
JEP 305新增了使instanceof運(yùn)算符具有模式匹配的能力。模式匹配能夠使程序的通用邏輯更加簡潔,代碼更加簡單,同時(shí)在做類型判斷和類型轉(zhuǎn)換的時(shí)候也更加安全,接下來我們來詳細(xì)講解一下。
幾乎每個(gè)程序員都見過如下代碼,在包含判斷表達(dá)式是否具有某種類型的邏輯時(shí),程序會(huì)對(duì)不同類型進(jìn)行不同的處理。我么來看一下熟悉的instanceof-and-cast用法:
// 在方法的入口接收一個(gè)對(duì)象 public void beforeWay(Object obj) { // 通過instanceof判斷obj對(duì)象的真實(shí)數(shù)據(jù)類型是否是String類型 if (obj instanceof String) { // 如果進(jìn)來了,說明obj的類型是String類型,直接進(jìn)行強(qiáng)制類型轉(zhuǎn)換。 String str = (String) obj; // 輸出字符串的長度 System.out.println(str.length()); } }
這段程序做了3件事:
1. 先判斷obj的真實(shí)數(shù)據(jù)類型
2. 判斷成立后進(jìn)行了強(qiáng)制類型轉(zhuǎn)換(將對(duì)象obj強(qiáng)制類型轉(zhuǎn)換為String)
3. 聲明一個(gè)新的本地變量str,指向上面的obj
這種模式的邏輯并不復(fù)雜,并且?guī)缀跛蠮ava程序員都可以理解。但是出于以下原因,上述做法并不是最理想的:
1. 語法臃腫乏味
2. 同時(shí)執(zhí)行類型檢測校驗(yàn)和類型轉(zhuǎn)換。
3. String類型在程序中出現(xiàn)了3次,但是最終要的可能只是一個(gè)字符串類型的對(duì)象變量而已。
4. 重復(fù)的代碼過多,冗余度較高。
JDK 14提供了新的解決方案:新的instanceof模式匹配 ,新的模式匹配的用法如下所示,在`instanceof`的類型之后添加了變量`str`。如果`instanceof`對(duì)`obj`的類型檢查通過,`obj`會(huì)被轉(zhuǎn)換成`str`表示的`String`類型。在新的用法中,`String`類型僅出現(xiàn)一次。
public void patternMatching(Object obj) { if (obj instanceof String str) { // can use str here System.out.println(str.length()); } else { // can't use str here } }
上述代碼需要注意:
如果obj是String類型,則將obj類型轉(zhuǎn)換為String,并將其賦值給變量str。綁定的變量作用域?yàn)閕f語句內(nèi)部,并不在false語句塊內(nèi)。
接下來我們看一下它的另一個(gè)做法:
通常`equals()`方法的實(shí)現(xiàn)都會(huì)先檢查目標(biāo)對(duì)象的類型。`instanceof`的模式匹配可以簡化`equals()`方法的實(shí)現(xiàn)邏輯。下面代碼中的Student類展示了相關(guān)的用法。
public class Student { private String name ; public Student(String name) { this.name = name; } // @Override // public boolean equals(Object o) { // if (this == o) return true; // if (o == null || getClass() != o.getClass()) return false; // Student student = (Student) o; // return Objects.equals(name, student.name); // } // 簡化后做法! @Override public boolean equals(Object obj) { return (obj instanceof Student s) && Objects.equals(this.name, s.name); } @Override public int hashCode() { return Objects.hash(name); } }
總結(jié)instanceof運(yùn)算符“匹配”規(guī)則如下:
- 如果obj是String類型,則將obj類型轉(zhuǎn)換為String,并將其賦值給變量str。綁定的變量作用域?yàn)閕f語句內(nèi)部,并不在false語句塊內(nèi)。
- 到這兒,有一定Java基礎(chǔ)的同學(xué)應(yīng)該看出來的JDK 14后的instanceof的模式匹配極大的簡化了類型檢查和轉(zhuǎn)型的問題。
JEP 361: Switch Expressions (Standard)
引入
擴(kuò)展switch分支選擇語句的寫法。Switch表達(dá)式在經(jīng)過JDK 12(JEP 325)和JDK(JEP 354)的預(yù)覽之后,在JDK 14中已經(jīng)穩(wěn)定可用。
設(shè)計(jì)初衷
Java的switch語句是一個(gè)變化較大的語法(可能是因?yàn)镴ava的switch語句一直不夠強(qiáng)大、熟悉swift或者js語言的同學(xué)可與swift的switch語句對(duì)比一下,就會(huì)發(fā)現(xiàn)Java的switch相對(duì)較弱),因?yàn)镴ava的很多版本都在不斷地改進(jìn)switch語句:JDK 12擴(kuò)展了switch語句,使其可以用作語句或者表達(dá)式,并且傳統(tǒng)的和擴(kuò)展的簡化版switch都可以使用。
JDK 12對(duì)于switch的增強(qiáng)主要在于簡化書寫形式,提升功能點(diǎn)。
下面簡單回顧一下switch的進(jìn)化階段:
從Java 5+開始,Java的switch語句可使用枚舉了。
從Java 7+開始,Java的switch語句支持使用String類型的變量和表達(dá)式了。
從Java 11+開始,Java的switch語句會(huì)自動(dòng)對(duì)省略break導(dǎo)致的貫穿提示警告(以前需要使用-X:fallthrough選項(xiàng)才能顯示出來) 。
但從JDK12開始,Java的switch語句有了很大程度的增強(qiáng)。
JDK 14的該JEP是從[JEP 325](https://openjdk.java.net/jeps/325)和[JEP 354](https://openjdk.java.net/jeps/354)演變而來的。但是,此JEP 361 Switch表達(dá)式 (標(biāo)準(zhǔn))是獨(dú)立的,并且不依賴于這兩個(gè)JEP。
以前的switch程序
代碼如下:
public class Demo01{ public static void main(String[] args){ // 聲明變量score,并為其賦值為'C' var score = 'C'; // 執(zhí)行switch分支語句 switch (score) { case 'A': System.out.println("優(yōu)秀"); break; case 'B': System.out.println("良好"); break; case 'C': System.out.println("中"); break; case 'D': System.out.println("及格"); break; case 'E': System.out.println("不及格"); break; default: System.out.println("數(shù)據(jù)非法!"); } } }
這是經(jīng)典的Java 11以前的switch寫法 ,這里不能忘記寫break,否則switch就會(huì)貫穿、導(dǎo)致程序出現(xiàn)錯(cuò)誤(JDK 11會(huì)提示警告)。
在JDK 12之前如果switch忘記寫break將導(dǎo)致貫穿,在JDK 12對(duì)switch的這一貫穿性做了改進(jìn)。你只要將case后面的冒號(hào)(:)改成箭頭,那么你即使不寫break也不會(huì)貫穿了,因此上面程序可改寫如下形式:
public class Demo02{ public static void main(String[] args){ // 聲明變量score,并為其賦值為'C' var score = 'C'; // 執(zhí)行switch分支語句 switch (score){ case 'A' -> System.out.println("優(yōu)秀"); case 'B' -> System.out.println("良好"); case 'C' -> System.out.println("中"); case 'D' -> System.out.println("及格"); case 'E' -> System.out.println("不及格"); default -> System.out.println("成績數(shù)據(jù)非法!"); } } }
上面代碼簡潔很多了。
JDK 14的switch表達(dá)式
JDK 12之后的switch甚至可作為表達(dá)式了——不再是單獨(dú)的語句。例如如下程序。
public class Demo03 { public static void main(String[] args) { // 聲明變量score,并為其賦值為'C' var score = 'C'; // 執(zhí)行switch分支語句 String s = switch (score) { case 'A' -> "優(yōu)秀"; case 'B' -> "良好"; case 'C' -> "中"; case 'D' -> "及格"; case 'F' -> "不及格"; default -> "成績輸入錯(cuò)誤"; }; System.out.println(s); } }
上面程序直接將switch表達(dá)式的值賦值給s變量,這樣switch不再是一個(gè)語句,而是一個(gè)表達(dá)式.
當(dāng)你把switch中的case后的冒號(hào)改為箭頭之后,此時(shí)switch就不會(huì)貫穿了,但在某些情況下,程序本來就希望貫穿比如我就希望兩個(gè)case共用一個(gè)執(zhí)行體!JDK 12之后的switch中的case也支持多值匹配,這樣程序就變得更加簡潔了。例如如下程序。
public class Demo04 { public static void main(String[] args) { // 聲明變量score,并為其賦值為'C' var score = 'B'; // 執(zhí)行switch分支語句 String s = switch (score) { case 'A', 'B' -> "上等"; case 'C' -> "中等"; case 'D', 'E' -> "下等"; default -> "成績數(shù)據(jù)輸入非法!"; }; System.out.println(s); } }
當(dāng)使用箭頭標(biāo)簽時(shí),箭頭標(biāo)簽右邊可以是表達(dá)式、`throw`語句或是代碼塊。如果是代碼塊,需要使用`yield`語句來返回值。下面代碼中的print方法中的`default`語句的右邊是一個(gè)代碼塊。在代碼塊中使用`yield`來返回值。,JDK 14引入了一個(gè)新的`yield`語句來產(chǎn)生一個(gè)值,該值成為封閉的switch表達(dá)式的值。
public void print(int days) { // 聲明變量score,并為其賦值為'C' var score = 'B'; String result = switch (score) { case 'A', 'B' -> "上等"; case 'C' -> "中等"; case 'D', 'E' -> "下等"; default -> { if (score > 100) { yield "數(shù)據(jù)不能超過100"; } else { yield score + "此分?jǐn)?shù)低于0分"; } } }; System.out.println(result); }
在switch表達(dá)式中不能使用break。switch表達(dá)式的每個(gè)標(biāo)簽都必須產(chǎn)生一個(gè)值,或者拋出異常。switch表達(dá)式必須窮盡所有可能的值。這意味著通常需要一個(gè)default語句。一個(gè)例外是枚舉類型。如果窮盡了枚舉類型的所有可能值,則不需要使用default。在這種情況下,編譯器會(huì)自動(dòng)生成一個(gè)default語句。這是因?yàn)槊杜e類型中的枚舉值可能發(fā)生變化。比如,枚舉類型Color 中原來只有3個(gè)值:RED、GREEN和BLUE。使用該枚舉類型的switch表達(dá)式窮盡了3種情況并完成編譯。之后Color中增加了一個(gè)新的值YELLOW,當(dāng)用這個(gè)新的值調(diào)用之前的代碼時(shí),由于不能匹配已有的值,編譯器產(chǎn)生的default會(huì)被調(diào)用,告知枚舉類型發(fā)生改變
引入
在Java的開發(fā)過程中,通常需要進(jìn)行大量字符串文字的拼接,等相關(guān)組織操作,從JDK 13到JDK 14開始文本塊新特性的提出,提高了Java程序書寫大段字符串文本的可讀性和方便性。
設(shè)計(jì)初衷
文本塊功能在JDK 13中作為預(yù)覽功能(JEP 355)被引入。這個(gè)功能在JDK 14中得到了更新。文本塊是使用3個(gè)引號(hào)分隔的多行字符串。
描述
文本塊是Java語言的新語法,可以用來表示任何字符串,具有更高的表達(dá)能力和更少的復(fù)雜度。文本塊的開頭定界符是由三個(gè)雙引號(hào)字符(""")組成的序列,后面跟0個(gè)或多個(gè)空格,最后跟一個(gè)行終止符。內(nèi)容從開頭定界符的行終止符之后的第一個(gè)字符開始。結(jié)束定界符是三個(gè)雙引號(hào)字符的序列。內(nèi)容在結(jié)束定界符的第一個(gè)雙引號(hào)之前的最后一個(gè)字符處結(jié)束。與字符串文字中的字符不同,文本塊的內(nèi)容中可以直接包含雙引號(hào)字符。當(dāng)然也允許在文本塊中使用\“,但不是必需的或不建議使用。與字符串文字中的字符不同,內(nèi)容可以直接包含行終止符。允許在文本塊中使用\n,但不是必需或不建議使用。例如,文本塊:
line 1 line 2 line 3
等效于字符串文字:
"line 1\nline 2\nline 3\n"
或字符串文字的串聯(lián):
"line 1\n" + "line 2\n" + "line 3\n"
文本塊功能在JDK 13中作為預(yù)覽功能(JEP 355)被引入。這個(gè)功能在JDK 14中得到了更新。文本塊是使用3個(gè)引號(hào)分隔的多行字符串。根據(jù)文本塊在源代碼中的出現(xiàn)形式,多余的用于縮進(jìn)的白字符會(huì)被去掉。相應(yīng)的算法會(huì)在每一行中去掉同樣數(shù)量的白字符,直到其中任意的一行遇到非白字符為止。每一行字符串末尾的白字符會(huì)被自動(dòng)去掉。
下面代碼中的文本塊`xml`會(huì)被去掉前面的2個(gè)白字符的縮進(jìn)。
String xml = """ <root> <a>Hello</a> <b> <c> <d>World</d> </c> </b> </root> """;
需要注意的是,縮進(jìn)的去除是要考慮到作為結(jié)束分隔符的3個(gè)引號(hào)的位置的。如果把上面的文本塊改成下面代碼的格式,則沒有縮進(jìn)會(huì)被去掉。注意最后一行中3個(gè)引號(hào)的位置。去除的白字符的數(shù)量需要考慮到全部行中前導(dǎo)的白字符數(shù)量,包括最后一行。最終去除的白字符數(shù)量是這些行中前導(dǎo)白字符數(shù)量的最小值。
String xml2 = """ <root> <a>Hello</a> <b> <c> <d>World</d> </c> </b> </root> """;
在文本塊中同樣可以使用\n和\r這樣的轉(zhuǎn)義字符。除了String本身支持的轉(zhuǎn)義字符之外,文本塊還支持2個(gè)特殊的轉(zhuǎn)義字符:
- \:阻止插入換行符。
- \s:表示一個(gè)空格。可以用來避免末尾的白字符被去掉。
由于\的使用,下面代碼中的longString實(shí)際上只有一行。
String longString = """ hello \ world \ goodbye """;
在下面的代碼中,通過使用\s,每一行的長度都為10。
String names = """ alex \s bob \s long name\s """;
使用原始字符串語法:
String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n";
使用文本塊文本塊語法:
String html = """ <html> <body> <p>Hello, world</p> </body> </html> """;
使用原始的字符串語法:
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" + "WHERE `CITY` = 'INDIANAPOLIS'\n" + "ORDER BY `EMP_ID`, `LAST_NAME`;\n";
使用文本塊語法:
String query = """ SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` WHERE `CITY` = 'INDIANAPOLIS' ORDER BY `EMP_ID`, `LAST_NAME`; """;
多語言示例
使用原始的字符串語法:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval("function hello() {\n" + " print('\"Hello, world\"');\n" + "}\n" + "\n" + "hello();\n");
使用文本塊語法:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); Object obj = engine.eval(""" function hello() { print('"Hello, world"'); } hello(); """);