Java 17 新特性尝鲜
JDK 17更新了包括14個(gè)特性,具體如下表所示:
- Restore Always-StrictFloating-Point Semantics?恢復(fù)始終嚴(yán)格模式(Always-Strict)的浮點(diǎn)語義
- EnhancedPseudo-Random Number Generators? 增強(qiáng)型偽隨機(jī)數(shù)生成器
- New macOS RenderingPipeline?新增macOS渲染管道
- macOS/AArch64 Port?支持將JDK移植到macOS或AArch64
- Deprecate the AppletAPI for Removal?棄用待移除的Applet API
- Strongly EncapsulateJDK Internals?強(qiáng)封裝JDK內(nèi)部API
- Pattern Matching forswitch (Preview)?switch模式匹配進(jìn)入預(yù)覽(Preview)階段
- Remove RMI Activation?移除RMI(遠(yuǎn)程方法調(diào)用)激活機(jī)制
- Sealed Classes?密封類
- Remove theExperimental AOT and JIT Compiler?移除實(shí)驗(yàn)性AOT和JIT編譯器
- Deprecate theSecurity Manager for Removal?棄用待移除的安全管理器(Security Manager)
- Foreign Function& Memory API (Incubator)?外部函數(shù)和內(nèi)存API(孵化器)孵化階段
- Vector API (SecondIncubator)?Vector API(第二孵化器)第二孵化階段
- Context-SpecificDeserialization Filters?上下文特定的反序列化過濾器
至于Spring,其官方曾宣布Spring Framework 6.0和Spring Boot 3.0將基于JDK 17版本,預(yù)計(jì)2022年,下半年 發(fā)布 Spring Framework 6.0正式候選(RC)版本。
代碼層面
-
文本塊
-
switch表達(dá)式
-
record關(guān)鍵字
-
sealed classes密封類
-
instanceof模式匹配
-
Helpful NullPointerExceptions
-
日期周期格式化
-
精簡數(shù)字格式化支持
-
Stream.toList()簡化
文本塊
在Java17之前的版本里,定義一個(gè)字符串,比如一個(gè)JSON數(shù)據(jù),基本都是如下拼接方式定義:
public void lowVersion() { String text = "{\n" + " \"name\": \"小黑說Java\",\n" + " \"age\": 18,\n" + " \"address\": \"北京市西城區(qū)\"\n" + "}"; System.out.println(text); }通過Java 17中的類似的字符串處理則會方便很多;通過三個(gè)雙引號可以定義一個(gè)文本塊,并且結(jié)束的三個(gè)雙引號不能和開始的在同一行。文本塊語法代碼如下:
private void highVersion() {String text = """{"name":"小黑說Java", "age": 18, "address": "北京市西城區(qū)"}"""; System.out.println(text); }這段代碼的輸出結(jié)果是:
{ "name": "小黑說Java", "age": 18, "address": "北京市西城區(qū)" }switch表達(dá)式
Java 17版本中switch表達(dá)式將允許switch有返回值,并且可以直接作為結(jié)果賦值給一個(gè)變量,等等一系列的變化。
下面有一個(gè)switch例子,依賴于給定的枚舉值,執(zhí)行case操作,故意省略break。
private static void lowVesion(Fruit fruit) { switch (fruit) { case APPLE, PEAR: System.out.println("普通水果");break;case MANGO, AVOCADO: System.out.println("進(jìn)口水果"); break;default: System.out.println("未知水果"); } }調(diào)用這個(gè)方法傳入一個(gè)APPLE,會輸出以下結(jié)果:
普通水果 進(jìn)口水果 未知水果通過switch表達(dá)式來進(jìn)行簡化。將冒號(:)替換為箭頭(->),并且switch表達(dá)式默認(rèn)不會失敗,所以不需要break。
private static void withSwitchExpression(Fruit fruit) { switch (fruit) { case APPLE, PEAR -> System.out.println("普通水果"); case MANGO, AVOCADO -> System.out.println("進(jìn)口水果"); default -> System.out.println("未知水果"); } }switch表達(dá)式也可以返回一個(gè)值,比如上面的例子可以讓switch返回一個(gè)字符串來表示要打印的文本。需要注意在switch語句的最后要加一個(gè)分號。
private static void withReturnValue(Fruit fruit) { String text = switch (fruit) { case APPLE, PEAR -> "普通水果"; case MANGO, AVOCADO -> "進(jìn)口水果"; default -> "未知水果"; }; System.out.println(text); }也可以直接省略賦值動(dòng)作直接打印。
private static void withReturnValue(Fruit fruit) { System.out.println(switch (fruit) { case APPLE, PEAR -> "普通水果"; case MANGO, AVOCADO -> "進(jìn)口水果"; default -> "未知水果"; }); }如果想在case里想做不止一件事,比如在返回之前先進(jìn)行一些計(jì)算或者打印操作,可以通過大括號來作為case塊,最后的返回值使用關(guān)鍵字yield進(jìn)行返回。
private static void withYield(Fruit fruit) { String text = switch (fruit) { case APPLE, PEAR -> { System.out.println("給的水果是: " + fruit); yield "普通水果";} case MANGO, AVOCADO -> "進(jìn)口水果";default -> "未知水果"; }; System.out.println(text); }這個(gè)輸出結(jié)果是:
給的水果是: APPLE 普通水果也可以直接使用yield返回結(jié)果。
private static void oldStyleWithYield(Fruit fruit) { System.out.println(switch (fruit) {case APPLE, PEAR: yield "普通水果"; case MANGO, AVOCADO: yield "進(jìn)口水果"; default: yield "未知水果"; }); }record關(guān)鍵字
record用于創(chuàng)建不可變的數(shù)據(jù)類。在這之前如果需要?jiǎng)?chuàng)建一個(gè)存放數(shù)據(jù)的類,通常需要先創(chuàng)建一個(gè)Class,然后生成構(gòu)造方法、getter、setter、hashCode、equals和toString等這些方法,或者使用Lombok來簡化這些操作。
比如定義一個(gè)Person類:
//這里使用lombok減少代碼 @Data @AllArgsConstructor public class Person {private String name; private int age; private String address; }通過Person類做一些測試,比如創(chuàng)建兩個(gè)對象,對他們進(jìn)行比較,打印這些操作。
public static void testPerson() { Person p1 = new Person("小黑說Java", 18, "北京市西城區(qū)"); Person p2 = new Person("小白說Java", 28, "北京市東城區(qū)"); System.out.println(p1); System.out.println(p2); System.out.println(p1.equals(p2)); }假設(shè)有一些場景只需要對Person的name和age屬性進(jìn)行打印,在有record之后將會變得非常容易。
public static void testPerson() { Person p1 = new Person("小黑說Java", 18, "北京市西城區(qū)"); Person p2 = new Person("小白說Java", 28, "北京市東城區(qū)"); // 使用record定義 record PersonRecord(String name,int age){} PersonRecord p1Record = new PersonRecord(p1.getName(), p1.getAge()); PersonRecord p2Record = new PersonRecord(p2.getName(), p2.getAge()); System.out.println(p1Record); System.out.println(p2Record); }record也可以單獨(dú)定義作為一個(gè)文件定義,但是因?yàn)镽ecord的使用非常緊湊,所以可以直接在需要使用的地方直接定義。
public record PersonRecord(String name, int age){}record同樣也有構(gòu)造方法,可以在構(gòu)造方法中對數(shù)據(jù)進(jìn)行一些驗(yàn)證操作。
public static void testPerson() { Person p1 = new Person("小黑說Java", 18, "北京市西城區(qū)"); Person p2 = new Person(null, 28, "北京市東城區(qū)"); record PersonRecord(String name, int age) { // 構(gòu)造方法 PersonRecord { System.out.println("name " + name + " age " + age); if (name == null) { throw new IllegalArgumentException("姓名不能為空");} } } PersonRecord p1Record = new PersonRecord(p1.getName(), p1.getAge()); PersonRecord p2Record = new PersonRecord(p2.getName(), p2.getAge()); }密封類 sealed class
密封類可以讓更好的控制哪些類可以對定義的類進(jìn)行擴(kuò)展。密封類可能對于框架或中間件的開發(fā)者更有用。在這之前一個(gè)類要么是可以被extends的,要么是final的,只有這兩種選項(xiàng)。
密封類可以控制有哪些類可以對超類進(jìn)行繼承,在Java 17之前如果需要控制哪些類可以繼承,可以通過改變類的訪問級別,比如去掉類的public,訪問級別為。比默認(rèn)如在com.everlight.java11包中定義了如下的三個(gè)類:
package com.everlight.java11; public abstract class Furit { } public class Apple extends Furit { } public class Pear extends Furit { }那么可以在另一個(gè)包c(diǎn)om.everlight.java11中寫如下的代碼:
private static void test() { Apple apple = new Apple(); Pear pear = new Pear(); Fruit fruit = apple; class Avocado extends Fruit {}; }既可以定義Apple,Pear,也可以將apple實(shí)例賦值給Fruit,并且可以對Fruit進(jìn)行繼承。
如果不想讓Fruit在com.everlight.java11包以外被擴(kuò)展,在Java11版本中只能改變訪問權(quán)限,去掉class的public修飾符。這樣雖然可以控制被被繼承,但是也會導(dǎo)致Fruit fruit = apple;也編譯失敗;在Java 17中通過密封類可以解決這個(gè)問題。
package com.everlight.java17; public abstract sealed class Furit permits Apple,Pear { } public non-sealed class Apple extends Furit { } public final class Pear extends Furit { }在定義Furit時(shí)通過關(guān)鍵字sealed聲明為密封類,通過permits可以指定Apple,Pear類可以進(jìn)行繼承擴(kuò)展。
子類需要指明它是final,non-sealed或sealed的。父類不能控制子類是否可以被繼承。
private static void test() { Apple apple = new Apple(); Pear pear = new Pear(); // 可以將apple賦值給Fruit Fruit fruit = apple; // 只能繼承Apple,不能繼承Furit class Avocado extends Apple {}; }instanceof模式匹配
通常使用instanceof時(shí),一般發(fā)生在需要對一個(gè)變量的類型進(jìn)行判斷,如果符合指定的類型,則強(qiáng)制類型轉(zhuǎn)換為一個(gè)新變量。
private static void oldStyle(Object o) { if (o instanceof Furit) { Furit furit = (GrapeClass) o; System.out.println("This furit is :" + furit.getName); } }在使用instanceof的模式匹配后,上面的代碼可進(jìn)行簡寫。
private static void oldStyle(Object o) { if (o instanceof Furit furit) { System.out.println("This furit is :" + furit.getName); } }可以將類型轉(zhuǎn)換和變量聲明都在if中處理。同時(shí),可以直接在if中使用這個(gè)變量。
private static void oldStyle(Object o) { if (o instanceof Furit furit && furit.getColor()==Color.RED) { System.out.println("This furit is :" + furit.getName); } }因?yàn)橹挥挟?dāng)instanceof的結(jié)果為true時(shí),才會定義變量furit,所以這里可以使用&&,但是改為||就會編譯報(bào)錯(cuò)。
Helpful NullPointerExceptions
Helpful NullPointerExceptions可以在遇到NPE時(shí)節(jié)省一些分析時(shí)間。如下的代碼會導(dǎo)致一個(gè)NPE。
public static void main(String[] args) { Person p = new Person(); String cityName = p.getAddress().getCity().getName(); }在Java 11中,輸出將顯示NullPointerException發(fā)生的行號,但不知道哪個(gè)方法調(diào)用時(shí)產(chǎn)生的null,必須通過調(diào)試的方式找到。
Exception in thread "main" java.lang.NullPointerException at com.everlight.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java :13)在Java 17中,則會準(zhǔn)確顯示發(fā)生NPE的精確位置。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.everlight.java17.Address.getCity()" because the return value of "com.everlight.java17.Person.getAddress()" is null at com.everlight.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java :13)日期周期格式化
在Java 17中添加了一個(gè)新的模式B,用于格式化DateTime,它根據(jù)Unicode標(biāo)準(zhǔn)指示一天時(shí)間段。
使用默認(rèn)的英語語言環(huán)境,打印一天的幾個(gè)時(shí)刻:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("B"); System.out.println(dtf.format(LocalTime.of(8, 0))); System.out.println(dtf.format(LocalTime.of(13, 0))); System.out.println(dtf.format(LocalTime.of(20, 0))); System.out.println(dtf.format(LocalTime.of(23, 0))); System.out.println(dtf.format(LocalTime.of(0, 0)));輸出結(jié)果:
in the morning in the afternoon in the evening at night midnight如果是中文語言環(huán)境,則輸出結(jié)果為:
上午 下午 晚上 晚上 午夜可見java17的晚上是包括英美國家的evening和night的。
精簡數(shù)字格式化支持
在NumberFormat中添加了一個(gè)工廠方法,可以根據(jù)Unicode標(biāo)準(zhǔn)以緊湊的、人類可讀的形式格式化數(shù)字。
short格式如下所示:
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT); System.out.println(fmt.format(1000)); System.out.println(fmt.format(100000)); System.out.println(fmt.format(1000000));輸出格式為:
1K 100K 1Mlong格式如下所示:
fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG); System.out.println(fmt.format(1000)); System.out.println(fmt.format(100000)); System.out.println(fmt.format(1000000));輸出結(jié)果為:
1 thousand 100 thousand 1 millionStream.toList()
如果需要將Stream轉(zhuǎn)換成List,需要通過調(diào)用collect方法使用Collectors.toList(),代碼非常冗長。
private static void oldStyle() { Stream<String> stringStream = Stream.of("a", "b", "c"); List<String> stringList = stringStream.collect(Collectors.toList()); for(String s : stringList) { System.out.println(s); } }在Java 17中將會變得簡單,可以直接調(diào)用toList()。
private static void streamToList() { Stream<String> stringStream = Stream.of("a", "b", "c");List<String> stringList = stringStream.toList();for(String s : stringList) { System.out.println(s); } }?總結(jié)
以上是生活随笔為你收集整理的Java 17 新特性尝鲜的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 静态切换开关,静态转换开关,静态开关,双
- 下一篇: Baumer工业相机堡盟工业相机如何通过