Java 17 與 Java 11 相比有什麼變化?

信碼由韁發表於2021-10-15

【注】本文譯自: What’s New Between Java 11 and Java 17?

    9 月 14 日 Java 17 釋出。是時候仔細看看自上一個 LTS 版本(即 Java 11)以來的變化。我們先簡要介紹許可模型,然後重點介紹 Java 11 和 Java 17 之間的一些變化,主要是通過 例子。享受吧!

1. 介紹

    首先,讓我們仔細看看 Java 許可和支援模型。Java 17 是一個 LTS(長期支援)版本,就像 Java 11 一樣。Java 11 開始了一個新的釋出節奏。Java 11 支援到 2023 年 9 月,擴充套件支援到 2026 年 9 月。此外,在 Java 11 中,Oracle JDK 不再免費用於生產和商業用途。每 6 個月釋出一個新的 Java 版本,即所謂的非 LTS 釋出,從 Java 12 直至幷包括 Java 16。但是,這些都是生產就緒版本。與 LTS 版本的唯一區別是支援在下一個版本釋出時結束。例如。 Java 12 的支援在 Java 13 釋出時結束。當您想要保持支援時,您或多或少必須升級到 Java 13。當您的某些依賴項尚未為 Java 13 做好準備時,這可能會導致一些問題。大多數情況下,對於生產用途,公司將等待 LTS 版本。但即便如此,一些公司也不願意升級。最近 Snyk 的一項調查顯示,只有 60% 的人在生產中使用 Java 11,而這距離 Java 11 釋出已經過去了 3 年!60% 的公司仍在使用 Java 8。另一個值得注意的有趣事情是,下一個 LTS 版本將是 Java 21,它將在 2 年內釋出。關於庫在 Java 17 是否存在問題的一個很好的概述,可以在此處找到。

    隨著 Java 17 的推出,Oracle 許可模式發生了變化。Java 17 是根據新的 NFTC(Oracle 免費條款和條件)許可釋出的。因此,再次允許免費將 Oracle JDK 版本用於生產和商業用途。在同一個 Snyk 調查中,有人指出 Oracle JDK 版本在生產環境中僅被 23% 的使用者使用。請注意,對 LTS 版本的支援將在下一個 LTS 版本釋出一年後結束。看看這將如何影響升級到下一個 LTS 版本將會很有趣。

    Java 11 和 Java 17 之間發生了什麼變化?可以在 OpenJDK 網站上找到 JEP(Java 增強提案)的完整列表。在這裡,您可以閱讀每個 JEP 的詳細資訊。 有關自 Java 11 以來每個版本更改的完整列表,Oracle 發行說明提供了一個很好的概述。

    在接下來的部分中,將通過示例解釋一些更改,但主要取決於您對這些新功能進行試驗以熟悉它們。這篇文章中使用的所有資源都可以在 GitHub 上找到。

    最後一件事是 Oracle 釋出了 dev.java,所以不要忘記看一下。

2. Text Blocks(本文塊)

    為了使 Java 更具可讀性和更簡潔,已經進行了許多改進。文字塊無疑使程式碼更具可讀性。首先,我們來看看問題。假設您需要一些 JSON 字串到您的程式碼中並且您需要列印它。這段程式碼有幾個問題:

  • 雙引號的轉義;
  • 字串連線,使其具有或多或少的可讀性;
  • JSON 的複製貼上是一項勞動密集型的工作(您的 IDE 可能會幫助您解決該問題)。

      private static void oldStyle() {
          System.out.println("""
                  *************
                  * Old Style *
                  *************""");
          String text = "{\n" +
                        "  \"name\": \"John Doe\",\n" +
                        "  \"age\": 45,\n" +
                        "  \"address\": \"Doe Street, 23, Java Town\"\n" +
                        "}";
          System.out.println(text);
      }

    上面程式碼的輸出是格式良好的 JSON。

{
    "name": "John Doe",
    "age": 45,
    "address": "Doe Street, 23, Java Town"
}

    文字塊用三個雙引號定義,其中結尾的三個雙引號不能與起始的在同一行。首先,只需列印一個空塊。為了視覺化發生了什麼,文字被列印在兩個雙管之間。

    private static void emptyBlock() {
        System.out.println("""
                ***************
                * Empty Block *
                ***************""");
        String text = """
                """;
        System.out.println("|" + text + "|");
    }

    輸出是:

||||

有問題的 JSON 部分現在可以寫成如下,這樣可讀性更好。不需要轉義雙引號,它看起來就像會被列印。

    private static void jsonBlock() {
        System.out.println("""
                **************
                * Json Block *
                **************""");
        String text = """
                {
                  "name": "John Doe",
                  "age": 45,
                  "address": "Doe Street, 23, Java Town"
                }
                """;
        System.out.println(text);
    }

    輸出當然是相同的。

{
    "name": "John Doe",
    "age": 45,
    "address": "Doe Street, 23, Java Town"
}

    在前面的輸出中,沒有前面的空格。但是,在程式碼中,前面有空格。如何確定剝離前面的空格? 首先,將結尾的三個雙引號向左移動更多。

    private static void jsonMovedBracketsBlock() {
        System.out.println("""
                *****************************
                * Json Moved Brackets Block *
                *****************************""");
        String text = """
                  {
                    "name": "John Doe",
                    "age": 45,
                    "address": "Doe Street, 23, Java Town"
                  }
                """;
        System.out.println(text);
    }

    輸出現在在每行之前列印兩個空格。這意味著結尾的三個雙引號表示文字塊的開始。

{
    "name": "John Doe",
    "age": 45,
    "address": "Doe Street, 23, Java Town"
}
123

    當你將結尾的三個雙引號向右移動時會發生什麼?

    private static void jsonMovedEndQuoteBlock() {
        System.out.println("""
                ******************************
                * Json Moved End Quote Block *
                ******************************""");
        String text = """
                  {
                    "name": "John Doe",
                    "age": 45,
                    "address": "Doe Street, 23, Java Town"
                  }
                       """;
        System.out.println(text);
    }

    前面的間距現在由文字塊中的第一個非空格字元決定。

{
    "name": "John Doe",
    "age": 45,
    "address": "Doe Street, 23, Java Town"
}

3. Switch 表示式

    Switch 表示式將允許您從 switch 返回值並在賦值等中使用這些返回值。此處顯示了一個經典的 switch,其中,根據給定的 Fruit 列舉值,需要執行一些操作。故意忽略了 break。

    private static void oldStyleWithoutBreak(FruitType fruit) {
        System.out.println("""
                ***************************
                * Old style without break *
                ***************************""");
        switch (fruit) {
            case APPLE, PEAR:
                System.out.println("Common fruit");
            case ORANGE, AVOCADO:
                System.out.println("Exotic fruit");
            default:
                System.out.println("Undefined fruit");
        }
    }

    使用 APPLE 呼叫該方法。

oldStyleWithoutBreak(Fruit.APPLE);

    這將列印每個 case,因為沒有 break 語句,case 就失效了。

Common fruit
Exotic fruit
Undefined fruit

    因此,有必要在每個 case 中新增一個 break 語句,以防止這種失效。

    private static void oldStyleWithBreak(FruitType fruit) {
        System.out.println("""
                ************************
                * Old style with break *
                ************************""");
        switch (fruit) {
            case APPLE, PEAR:
                System.out.println("Common fruit");
                break;
            case ORANGE, AVOCADO:
                System.out.println("Exotic fruit");
                break;
            default:
                System.out.println("Undefined fruit");
        }
    }

    執行此方法會為您提供所需的結果,但現在程式碼的可讀性稍差。

Common fruit

    這可以通過使用 Switch 表示式來解決。用箭頭 (->) 替換冒號 (:) 並確保在大小寫中使用表示式。Switch 表示式的預設行為是沒有失敗,因此不需要 break。

    private static void withSwitchExpression(FruitType fruit) {
        System.out.println("""
                **************************
                * With switch expression *
                **************************""");
        switch (fruit) {
            case APPLE, PEAR -> System.out.println("Common fruit");
            case ORANGE, AVOCADO -> System.out.println("Exotic fruit");
            default -> System.out.println("Undefined fruit");
        }
    }

    這已經不那麼囉嗦了,結果是相同的。

    Switch 表示式也可以返回一個值。在上面的示例中,您可以返回 String 值並將它們分配給變數 text。在此之後,可以列印 text 本變數。不要忘記在最後一個案例括號後新增一個分號。

    private static void withReturnValue(FruitType fruit) {
        System.out.println("""
                *********************
                * With return value *
                *********************""");
        String text = switch (fruit) {
            case APPLE, PEAR -> "Common fruit";
            case ORANGE, AVOCADO -> "Exotic fruit";
            default -> "Undefined fruit";
        };
        System.out.println(text);
    }

    而且,更短的是,上面的內容可以用一個語句重寫。這是否比上面的更具可讀性取決於您。

    private static void withReturnValueEvenShorter(FruitType fruit) {
        System.out.println("""
                **********************************
                * With return value even shorter *
                **********************************""");
        System.out.println(
            switch (fruit) {
                case APPLE, PEAR -> "Common fruit";
                case ORANGE, AVOCADO -> "Exotic fruit";
                default -> "Undefined fruit";
            });
    }

    當您需要在 case 中做不止一件事情時,您會怎麼做? 在這種情況下,您可以使用方括號來表示 case 塊,並在返回值時使用關鍵字 yield。

    private static void withYield(FruitType fruit) {
        System.out.println("""
                **************
                * With yield *
                **************""");
        String text = switch (fruit) {
            case APPLE, PEAR -> {
                System.out.println("the given fruit was: " + fruit);
                yield "Common fruit";
            }
            case ORANGE, AVOCADO -> "Exotic fruit";
            default -> "Undefined fruit";
        };
        System.out.println(text);
    }

    輸出現在有點不同,執行了兩個列印語句。

the given fruit was: APPLE
Common fruit

    您可以在“舊” switch 語法中使用 yield 關鍵字也很酷。這裡不需要 break。

    private static void oldStyleWithYield(FruitType fruit) {
        System.out.println("""
                ************************
                * Old style with yield *
                ************************""");
        System.out.println(switch (fruit) {
            case APPLE, PEAR:
                yield "Common fruit";
            case ORANGE, AVOCADO:
                yield "Exotic fruit";
            default:
                yield "Undefined fruit";
        });
    }

4. Records(記錄)

    Records 將允許您建立不可變的資料類。目前,您需要例如 使用 IDE 的自動生成函式建立 GrapeClass 以生成建構函式、getter、hashCode、equals 和 toString,或者您可以使用 Lombok 達到同樣的目的。最後,您會得到一些樣板程式碼,或者您的專案最終會依賴 Lombok。

public class GrapeClass {

    private final Color color;
    private final int nbrOfPits;

    public GrapeClass(Color color, int nbrOfPits) {
        this.color = color;
        this.nbrOfPits = nbrOfPits;
    }

    public Color getColor() {
        return color;
    }

    public int getNbrOfPits() {
        return nbrOfPits;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GrapeClass that = (GrapeClass) o;
        return nbrOfPits == that.nbrOfPits && color.equals(that.color);
    }

    @Override
    public int hashCode() {
        return Objects.hash(color, nbrOfPits);
    }

    @Override
    public String toString() {
        return "GrapeClass{" +
                "color=" + color +
                ", nbrOfPits=" + nbrOfPits +
                '}';
    }

}

    使用上述 GrapeClass 類執行一些測試。建立兩個例項,列印它們,比較它們,建立一個副本並也比較這個。

    private static void oldStyle() {
        System.out.println("""
                *************
                * Old style *
                *************""");
        GrapeClass grape1 = new GrapeClass(Color.BLUE, 1);
        GrapeClass grape2 = new GrapeClass(Color.WHITE, 2);
        System.out.println("Grape 1 is " + grape1);
        System.out.println("Grape 2 is " + grape2);
        System.out.println("Grape 1 equals grape 2? " + grape1.equals(grape2));
        GrapeClass grape1Copy = new GrapeClass(grape1.getColor(), grape1.getNbrOfPits());
        System.out.println("Grape 1 equals its copy? " + grape1.equals(grape1Copy));
    }

    測試的輸出是:

Grape 1 is GrapeClass{color=java.awt.Color[r=0,g=0,b=255], nbrOfPits=1}
Grape 2 is GrapeClass{color=java.awt.Color[r=255,g=255,b=255], nbrOfPits=2}
Grape 1 equals grape 2? false
Grape 1 equals its copy? true

    GrapeRecord 具有與 GrapeClass 相同的功能,但要簡單得多。您建立一個記錄並指出欄位應該是什麼,然後您就完成了。

record GrapeRecord(Color color, int nbrOfPits) {
}

    一個記錄可以在它自己的檔案中定義,但是因為它非常緊湊,所以在需要的地方定義它也是可以的。上面用記錄重寫的測試變成如下:

    private static void basicRecord() {
        System.out.println("""
                ****************
                * Basic record *
                ****************""");
        record GrapeRecord(Color color, int nbrOfPits) {}
        GrapeRecord grape1 = new GrapeRecord(Color.BLUE, 1);
        GrapeRecord grape2 = new GrapeRecord(Color.WHITE, 2);
        System.out.println("Grape 1 is " + grape1);
        System.out.println("Grape 2 is " + grape2);
        System.out.println("Grape 1 equals grape 2? " + grape1.equals(grape2));
        GrapeRecord grape1Copy = new GrapeRecord(grape1.color(), grape1.nbrOfPits());
        System.out.println("Grape 1 equals its copy? " + grape1.equals(grape1Copy));
    }

    輸出與上面相同。重要的是要注意記錄的副本應該以相同的副本結束。新增額外的功能,例如 grape1.nbrOfPits() 為了做一些處理並返回與初始 nbrOfPits 不同的值是一種不好的做法。雖然這是允許的,但您不應該這樣做。

    建構函式可以通過一些欄位驗證進行擴充套件。請注意,將引數分配給記錄欄位發生在建構函式的末尾。

    private static void basicRecordWithValidation() {
        System.out.println("""
                ********************************
                * Basic record with validation *
                ********************************""");
        record GrapeRecord(Color color, int nbrOfPits) {
            GrapeRecord {
                System.out.println("Parameter color=" + color + ", Field color=" + this.color());
                System.out.println("Parameter nbrOfPits=" + nbrOfPits + ", Field nbrOfPits=" + this.nbrOfPits());
                if (color == null) {
                    throw new IllegalArgumentException("Color may not be null");
                }
            }
        }
        GrapeRecord grape1 = new GrapeRecord(Color.BLUE, 1);
        System.out.println("Grape 1 is " + grape1);
        GrapeRecord grapeNull = new GrapeRecord(null, 2);
    }

    上述測試的輸出向您展示了此功能。 在建構函式內部,欄位值仍然為 null,但在列印記錄時,它們被分配了一個值。驗證也做它應該做的事情,並在顏色為 null 時丟擲 IllegalArgumentException。

Parameter color=java.awt.Color[r=0,g=0,b=255], Field color=null
Parameter nbrOfPits=1, Field nbrOfPits=0
Grape 1 is GrapeRecord[color=java.awt.Color[r=0,g=0,b=255], nbrOfPits=1]
Parameter color=null, Field color=null
Parameter nbrOfPits=2, Field nbrOfPits=0
Exception in thread "main" java.lang.IllegalArgumentException: Color may not be null
    at com.mydeveloperplanet.myjava17planet.Records$2GrapeRecord.<init>(Records.java:40)
    at com.mydeveloperplanet.myjava17planet.Records.basicRecordWithValidation(Records.java:46)
    at com.mydeveloperplanet.myjava17planet.Records.main(Records.java:10)

5. Sealed Classes(密封類)

    密封類將讓您更好地控制哪些類可以擴充套件您的類。密封類可能更像是一個對庫所有者有用的功能。一個類在 Java 11 final 中或者可以擴充套件。如果您想控制哪些類可以擴充套件您的超類,您可以將所有類放在同一個包中,並賦予超類包可見性。現在一切都在您的控制之下,但是,不再可能從包外部訪問超類。讓我們通過一個例子來看看這是如何工作的。

    在包
com.mydeveloperplanet.myjava17planet.nonsealed 中建立一個具有公共可見性的抽象類 Fruit。在同一個包中,建立了最終的類 Apple 和 Pear,它們都擴充套件了 Fruit。

public abstract class Fruit {
}
public final class Apple extends Fruit {
}
public final class Pear extends Fruit {
}

    在包
com.mydeveloperplanet.myjava17planet 中建立一個帶有 problemSpace 方法的 SealedClasses.java 檔案。如您所見,可以為 Apple、 Pear 和 Apple 建立例項,可以將 Apple 分配給 Fruit。除此之外,還可以建立一個擴充套件 Fruit 的 Avocado 類。

public abstract sealed class FruitSealed permits AppleSealed, PearSealed {
}
public non-sealed class AppleSealed extends FruitSealed {
}
public final class PearSealed extends FruitSealed {
}

    假設您不希望有人擴充套件 Fruit。 在這種情況下,您可以將 Fruit 的可見性更改為預設可見性(刪除 public 關鍵字)。在將 Apple分配給 Fruit 和建立 Avocado 類時,上述程式碼將不再編譯。後者是需要的,但我們確實希望能夠將一個 Apple 分配給一個 Fruit。這可以在帶有密封類的 Java 17 中解決。

在包
com.mydeveloperplanet.myjava17planet.sealed 中,建立了 Fruit、Apple 和 Pear 的密封版本。唯一要做的就是將 sealed 關鍵字新增到 Fruit 類中,並使用 permits 關鍵字指示哪些類可以擴充套件此 Sealed 類。子類需要指明它們是 final、 sealed 還是 non-sealed。超類無法控制子類是否可以擴充套件以及如何擴充套件。

public abstract sealed class FruitSealed permits AppleSealed, PearSealed {
}
public non-sealed class AppleSealed extends FruitSealed {
}
public final class PearSealed extends FruitSealed {
}

    在 sealedClasses 方法中,仍然可以將 AppleSealed 分配給 FruitSealed,但 Avocado 不允許擴充套件 FruitSealed。 然而,允許擴充套件 AppleSealed 因為這個子類被指示為非密封的。

    private static void sealedClasses() {
        AppleSealed apple = new AppleSealed();
        PearSealed pear = new PearSealed();
        FruitSealed fruit = apple;
        class Avocado extends AppleSealed {};
    }

6. instanceof 的模式匹配

    通常需要檢查物件是否屬於某種型別,如果是,首先要做的是將物件強制轉換為該特定型別的新變數。可以在以下程式碼中看到一個示例:

private static void oldStyle() {
System.out.println("""
 *************
 * Old Style *
 *************""");
Object o = new GrapeClass(Color.BLUE, 2);
if (o instanceof GrapeClass) {
GrapeClass grape = (GrapeClass) o;
System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
 }
 }

    輸出是:

This grape has 2 pits.

    使用 instanceof 的模式匹配,上面的可以改寫如下。如您所見,可以在 instanceof 檢查中建立變數,並且不再需要用於建立新變數和轉換物件的額外行。

    private static void patternMatching() {
        System.out.println("""
                ********************
                * Pattern matching *
                ********************""");
        Object o = new GrapeClass(Color.BLUE, 2);
        if (o instanceof GrapeClass grape) {
            System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
        }
    }

    輸出當然與上面相同。

    仔細檢視變數的範圍很重要。它不應該是模稜兩可的。在下面的程式碼中,&& 之後的條件只會在 instanceof 檢查結果為 true 時進行評估。 所以這是允許的。將 && 更改為 || 不會編譯。

    private static void patternMatchingScope() {
        System.out.println("""
                *******************************
                * Pattern matching scope test *
                *******************************""");
        Object o = new GrapeClass(Color.BLUE, 2);
        if (o instanceof GrapeClass grape && grape.getNbrOfPits() == 2) {
            System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
        }
    }

    下面的程式碼顯示了另一個有關範圍的示例。如果物件不是 GrapeClass 型別,則丟擲 RuntimeException。在這種情況下,永遠不會到達列印語句。在這種情況下,也可以使用 grape 變數,因為編譯器肯定知道 grape 存在。

    private static void patternMatchingScopeException() {
        System.out.println("""
                **********************************************
                * Pattern matching scope test with exception *
                **********************************************""");
        Object o = new GrapeClass(Color.BLUE, 2);
        if (!(o instanceof  GrapeClass grape)) {
            throw new RuntimeException();
        }
        System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
    }

7.有用的空指標異常

    有用的 NullPointerException 將為您節省一些寶貴的分析時間。以下程式碼導致 NullPointerException。

public class HelpfulNullPointerExceptions {

    public static void main(String[] args) {
        HashMap<String, GrapeClass> grapes = new HashMap<>();
        grapes.put("grape1", new GrapeClass(Color.BLUE, 2));
        grapes.put("grape2", new GrapeClass(Color.white, 4));
        grapes.put("grape3", null);
        var color = ((GrapeClass) grapes.get("grape3")).getColor();
    }
}

    對於 Java 11,輸出將顯示 NullPointerException 發生的行號,但您不知道哪個鏈式方法解析為 null。你必須通過除錯的方式找到自己。

Exception in thread "main" java.lang.NullPointerException
        at com.mydeveloperplanet.myjava17planet.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

    在 Java 17 中,相同的程式碼會產生以下輸出,其中準確顯示了 NullPointerException 發生的位置。

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.mydeveloperplanet.myjava17planet.GrapeClass.getColor()" because the return value of "java.util.HashMap.get(Object)" is null
    at com.mydeveloperplanet.myjava17planet.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

 8. 精簡數字格式支援

    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
1M

    LONG 格式樣式:

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 million
荷蘭語替換英語的 LONG 格式:
fmt = NumberFormat.getCompactNumberInstance(Locale.forLanguageTag("NL"), NumberFormat.Style.LONG);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));

    輸出是:

1 duizend
100 duizend
1 miljoen

9. 新增了日週期支援

    新增了一個新模式 B 用於格式化 DateTime,該模式根據 Unicode 標準指示日期時間段。

    使用預設的中文語言環境,列印一天中的幾個時刻:

System.out.println("""
 **********************
 * Chinese formatting *
 **********************""");
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)));

    輸出是:

上午
下午
晚上
晚上
午夜

    現在使用荷蘭語本地環境:

System.out.println("""
 ********************
 * Dutch formatting *
 ********************""");
dtf = DateTimeFormatter.ofPattern("B").withLocale(Locale.forLanguageTag("NL"));
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(0, 0)));
System.out.println(dtf.format(LocalTime.of(1, 0)));

    輸出如下。請注意,英國之夜從 23 點開始,荷蘭之夜從 01 點開始。可能是文化差異;-)。

’s ochtends
’s middags
’s avonds
middernacht
’s nachts

10. Stream.toList()

    為了將 Stream 轉換為 List,您需要使用 collect 的 Collectors.toList() 方法。這非常冗長,如下面的示例所示。

    private static void oldStyle() {
        System.out.println("""
                        *************
                        * Old style *
                        *************""");
        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() {
        System.out.println("""
                        *****************
                        * stream toList *
                        *****************""");
        Stream<String> stringStream = Stream.of("a", "b", "c");
        List<String> stringList =  stringStream.toList();
        for(String s : stringList) {
            System.out.println(s);
        }
    }

11. 結論

    在本文中,您快速瀏覽了自上一個 LTS 版本 Java 11 以來新增的一些功能。現在由您開始考慮遷移到 Java 17 的計劃,以及瞭解有關這些新功能的更多資訊以及您如何 可以將它們應用到您的日常編碼習慣中。提示:IntelliJ 會幫你解決這個問題!

相關文章