美文网首页
Java 17 与 Java 11 相比有什么变化?

Java 17 与 Java 11 相比有什么变化?

作者: 码者无疆 | 来源:发表于2021-10-15 19:24 被阅读0次

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

    image

        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 会帮你解决这个问题!

    相关文章

      网友评论

          本文标题:Java 17 与 Java 11 相比有什么变化?

          本文链接:https://www.haomeiwen.com/subject/ienmoltx.html