3

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

 2 years ago
source link: https://segmentfault.com/a/1190000040818817
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

    9 月 14 日 Java 17 发布。是时候仔细看看自上一个 LTS 版本(即 Java 11)以来的变化。我们先简要介绍许可模型,然后重点介绍 Java 11 和 Java 17 之间的一些变化,主要是通过 例子。享受吧!

    首先,让我们仔细看看 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 会帮你解决这个问题!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK