Java 17的一些新特性
Java 17是Java 11以來又一個LTS(長期支持)版本,Java 11 和Java 17之間發生了那些變化可以在OpenJDK官網找到JEP(Java增強建議)的完整列表。
文章會重點介紹在語法方面Java 17的更新,并且通過一些代碼示例讓大家更容易理解,主要涉及以下9個點:
- 文本塊
- switch表達式
- record關鍵字?
- ?密封類 sealed class
- instanceof模式匹配?
- Helpful NullPointerExceptions
- 日期周期格式化?
- 精簡數字格式化支持
- Stream.toList()?
文本塊
在Java17之前的版本里,如果我們需要定義一個字符串,比如一個JSON數據,基本都是如下方式定義:
public void lowVersion() {String text = "{\n" +" \"name\": \"小黑說Java\",\n" +" \"age\": 18,\n" +" \"address\": \"北京市西城區\"\n" +"}";System.out.println(text); }這種方式定義具有幾個問題:
- 雙引號需要進行轉義;
- 為了字符串的可讀性需要通過+號連接;
- 如果需要將JSON復制到代碼中需要做大量的格式調整(當然這一點也可以通過其他工具解決);
通過Java 17中的文本塊語法,類似的字符串處理則會方便很多;通過三個雙引號可以定義一個文本塊,并且結束的三個雙引號不能和開始的在同一行。
上面例子中的JSON可以更方便,可讀性更好的通過文本塊定義。代碼如下:
private void highVersion() {String text = """{"name": "小黑說Java","age": 18,"address": "北京市西城區"}""";System.out.println(text); }這段代碼的輸出結果是:
{"name": "小黑說Java","age": 18,"address": "北京市西城區" }switch表達式
Java 17版本中switch表達式將允許switch有返回值,并且可以直接作為結果賦值給一個變量,等等一系列的變化。
下面有一個switch例子,依賴于給定的枚舉值,執行case操作,故意省略break。
private static void lowVesion(Fruit fruit) {switch (fruit) {case APPLE, PEAR:System.out.println("普通水果");case MANGO, AVOCADO:System.out.println("進口水果");default:System.out.println("未知水果");} }我們調用這個方法傳入一個APPLE,會輸出以下結果:
普通水果 進口水果 未知水果?顯然這不是期望的結果,因為我們需要在每個case里添加break防止所有的case都沒執行。
private static void lowVesion(Fruit fruit) {switch (fruit) {case APPLE, PEAR:System.out.println("普通水果");break;case MANGO, AVOCADO:System.out.println("進口水果");break;default:System.out.println("未知水果");} }可以通過switch表達式來進行簡化。將冒號(:)替換為箭頭(->),并且switch表達式默認不會失敗,所以不需要break。
private static void withSwitchExpression(Fruit fruit) {switch (fruit) {case APPLE, PEAR -> System.out.println("普通水果");case MANGO, AVOCADO -> System.out.println("進口水果");default -> System.out.println("未知水果");} }switch表達式也可以返回一個值,比如上面的例子我們可以讓switch返回一個字符串來表示我們要打印的文本。需要注意在switch語句的最后要加一個分號。
private static void withReturnValue(Fruit fruit) {String text = switch (fruit) {case APPLE, PEAR -> "普通水果";case MANGO, AVOCADO -> "進口水果";default -> "未知水果";};System.out.println(text); }也可以直接省略賦值動作直接打印。
private static void withReturnValue(Fruit fruit) {System.out.println(switch (fruit) {case APPLE, PEAR -> "普通水果";case MANGO, AVOCADO -> "進口水果";default -> "未知水果";}); }如果你想在case里想做不止一件事,比如在返回之前先進行一些計算或者打印操作,可以通過大括號來作為case塊,最后的返回值使用關鍵字yield進行返回。
private static void withYield(Fruit fruit) {String text = switch (fruit) {case APPLE, PEAR -> {System.out.println("給的水果是: " + fruit);yield "普通水果";}case MANGO, AVOCADO -> "進口水果";default -> "未知水果";};System.out.println(text); }這個輸出結果是:
給的水果是: APPLE 普通水果當然也可以直接使用yield返回結果。
private static void oldStyleWithYield(Fruit fruit) {System.out.println(switch (fruit) {case APPLE, PEAR:yield "普通水果";case MANGO, AVOCADO:yield "進口水果";default:yield "未知水果";}); }record關鍵字?
record用于創建不可變的數據類。在這之前如果你需要創建一個存放數據的類,通常需要先創建一個Class,然后生成構造方法、getter、setter、hashCode、equals和toString等這些方法,或者使用Lombok來簡化這些操作。
比如定義一個Person類:
// 這里使用lombok減少代碼 @Data @AllArgsConstructor public class Person {private String name;private int age;private String address; }我們來通過Person類做一些測試,比如創建兩個對象,對他們進行比較,打印這些操作。
public static void testPerson() {Person p1 = new Person("小黑說Java", 18, "北京市西城區");Person p2 = new Person("小白說Java", 28, "北京市東城區");System.out.println(p1);System.out.println(p2);System.out.println(p1.equals(p2)); }假設有一些場景我們只需要對Person的name和age屬性進行打印,在有record之后將會變得非常容易。
public static void testPerson() {Person p1 = new Person("小黑說Java", 18, "北京市西城區");Person p2 = new Person("小白說Java", 28, "北京市東城區");// 使用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也可以單獨定義作為一個文件定義,但是因為Record的使用非常緊湊,所以可以直接在需要使用的地方直接定義。
record同樣也有構造方法,可以在構造方法中對數據進行一些驗證操作。
public static void testPerson() {Person p1 = new Person("小黑說Java", 18, "北京市西城區");Person p2 = new Person(null, 28, "北京市東城區");record PersonRecord(String name, int age) {// 構造方法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
密封類可以讓我們更好的控制哪些類可以對我定義的類進行擴展。密封類可能對于框架或中間件的開發者更有用。在這之前一個類要么是可以被extends的,要么是final的,只有這兩種選項。
密封類可以控制有哪些類可以對超類進行繼承,在Java 17之前如果我們需要控制哪些類可以繼承,可以通過改變類的訪問級別,比如去掉類的public,訪問級別為默認。比如我們在com.heiz.java11包中定義了如下的三個類:
package com.heiz.java11; public abstract class Furit { } public class Apple extends Furit { } public class Pear extends Furit { }那么我們可以在另一個包com.heiz123.java11中寫如下的代碼:
private static void test() {Apple apple = new Apple();Pear pear = new Pear();Fruit fruit = apple;class Avocado extends Fruit {}; }既可以定義Apple,Pear,也可以將apple實例賦值給Fruit,并且可以對Fruit進行繼承。
如果我們不想讓Fruit在com.heiz.java11包以外被擴展,在Java11版本中只能改變訪問權限,去掉class的public修飾符。這樣雖然可以控制被被繼承,但是也會導致Fruit fruit = apple;也編譯失敗;在Java 17中通過密封類可以解決這個問題。
package com.heiz.java17;public abstract sealed class Furit permits Apple,Pear { } public non-sealed class Apple extends Furit { } public final class Pear extends Furit {}在定義Furit時通過關鍵字sealed聲明為密封類,通過permits可以指定Apple,Pear類可以進行繼承擴展。
子類需要指明它是final,non-sealed或sealed的。父類不能控制子類是否可以被繼承。
private static void test() {Apple apple = new Apple();Pear pear = new Pear();// 可以將apple賦值給FruitFruit fruit = apple;// 只能繼承Apple,不能繼承Furitclass Avocado extends Apple {}; }instanceof模式匹配?
通常我們使用instanceof時,一般發生在需要對一個變量的類型進行判斷,如果符合指定的類型,則強制類型轉換為一個新變量。
private static void oldStyle(Object o) {if (o instanceof Furit) {Furit furit = (GrapeClass) o;System.out.println("This furit is :" + furit.getName);} }在使用instanceof的模式匹配后,上面的代碼可進行簡寫。
private static void oldStyle(Object o) {if (o instanceof Furit furit) {System.out.println("This furit is :" + furit.getName);} }可以將類型轉換和變量聲明都在if中處理。同時,可以直接在if中使用這個變量。
private static void oldStyle(Object o) {if (o instanceof Furit furit && furit.getColor()==Color.RED) {System.out.println("This furit is :" + furit.getName);} }因為只有當instanceof的結果為true時,才會定義變量furit,所以這里可以使用&&,但是改為||就會編譯報錯。
Helpful NullPointerExceptions
Helpful NullPointerExceptions可以在我們遇到NPE時節省一些分析時間。如下的代碼會導致一個NPE。
public static void main(String[] args) {Person p = new Person();String cityName = p.getAddress().getCity().getName(); }在Java 11中,輸出將顯示NullPointerException發生的行號,但不知道哪個方法調用時產生的null,必須通過調試的方式找到。
Exception in thread "main" java.lang.NullPointerExceptionat com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)在Java 17中,則會準確顯示發生NPE的精確位置。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.heiz.java17.Address.getCity()" because the return value of "com.heiz.java17.Person.getAddress()" is nullat com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExc日期周期格式化?
在Java 17中添加了一個新的模式B,用于格式化DateTime,它根據Unicode標準指示一天時間段。
使用默認的英語語言環境,打印一天的幾個時刻:
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)));輸出結果:
in the morning in the afternoon in the evening at night midnight如果是中文語言環境,則輸出結果為:
上午 下午 晚上 晚上 午夜可見咱們的晚上是包括英美國家的evening和night的。
精簡數字格式化支持
在NumberFormat中添加了一個工廠方法,可以根據Unicode標準以緊湊的、人類可讀的形式格式化數字。
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));輸出結果為:
1 thousand 100 thousand 1 millionStream.toList()?
如果需要將Stream轉換成List,需要通過調用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中將會變得簡單,可以直接調用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);} }總結
以上是生活随笔為你收集整理的Java 17的一些新特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android航班往返,酒店入住,离店时
- 下一篇: 在Java中实现SFTP协议文件传输的两