Java 17 新特性

本文详细介绍了Java 17的主要新特性,包括:恢复始终严格的浮点语义、增强的伪随机数生成器、新的macOS渲染管道、macOS/AArch64端口、Applet API的删除、强烈封装JDK内部、Switch支持模式匹配(预览版)、删除RMI激活、密封类、删除实验性质的AOT和JIT编译器、安全管理系统被标记为删除、外部函数和内存API(孵化器)、Vector API(第二个孵化器)以及特定于上下文的反序列化过滤器。同时,文章讨论了Java的LTS定义、新发布流程及其对开发者的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概述

本文我们将讨论与 Java SE 17 相关的新特性,包括新功能及其发布过程中的变化、LTS 支持和许可证。

2. JEP列表

首先,让我们将讨论一些兑Java开发人员息息相关的新特性

2.1 恢复始终严格的浮点语义 (JEP 306)

该 JEP 主要用于科学应用,它使浮点运算始终保持严格。默认的浮点运算是 strict 或 strictfp,两者都保证在每个平台上的浮点计算得到相同的结果。

在 Java 1.2 之前,strictfp 行为也是默认行为。然而,由于硬件问题,架构师发生了变化,关键字 strictfp 是重新启用这种行为所必需的。因此,不再需要使用此关键字。

2.2 增强的伪随机数生成器 (JEP 356)

还与更特殊的使用场景有关,JEP 356 为伪随机数生成器 (Pseudo-Random Number Generators - PRNG) 提供了新的接口和实现。

因此,可以更轻松地互换使用不同的算法,并且它还为基于流的编程提供了更好的支持:

public IntStream getPseudoInts(String algorithm, int streamSize) {
    // returns an IntStream with size @streamSize of random numbers generated using the @algorithm
    // where the lower bound is 0 and the upper is 100 (exclusive)
    return RandomGeneratorFactory.of(algorithm)
            .create(999)
            .ints(streamSize, 0, 100);
}

传统的随机类如 java.util.RandomSplittableRandomSecureRandom 现在也扩展了新的 RandomGenerator 接口。

2.3 新的 macOS 渲染管道 (JEP 382)

由于 Apple 弃用了 OpenGL API(在 macOS 10.14 中),此 JEP 为 macOS 实现了可在 Swing GUI 内部使用的 Java 2D 内部渲染管道。新实现使用 Apple Metal API,除了内部引擎之外,现有 API 没有任何变化。

2.4 macOS/AArch64 端口 (JEP 391)

Apple 宣布了一项将其计算机产品线从 X64 过渡到 AArch64 的长期计划。此 JEP 将 JDK 移植到 macOS 平台上的 AArch64 上运行。

2.5. Applet API 被标记为删除 (JEP 398)

尽管这对于许多使用 Applet API 开始其开发生涯的 Java 开发人员来说可能会感到难过,但许多 Web 浏览器已经取消了对 Java 插件的支持。随着 API 变得无关紧要,此版本将其标记为删除,即使它自版本 9 以来已被标记为已弃用。

2.6 强烈封装 JDK 内部 (JEP 403)

JEP 403 代表了向强烈封装 JDK 内部迈出的又一步,因为它删除了标志 –illegal-access。平台将忽略该标志,如果存在该标志,控制台将发出一条消息,通知该标志的终止。

java --illegal-access=warn

OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0  

此功能将阻止 JDK 用户访问内部 API,但 sun.misc.Unsafe 等关键 API 除外。

2.7 Switch 支持模式匹配(预览版)(JEP 406

这是通过增强 switch 表达式和语句的模式匹配向模式匹配迈出的又一步。它减少了定义这些表达式所需的样板文件,并提高了语言的表达力。

让我们看一下新功能的两个示例:

我们先来看一下 Java 17 之前是怎么做的

public String formatter(Object o) {
      String formatted = "animal";
      if (o instanceof Cat c && c.size() > 100) {
          formatted = "This is a cat";
      } else if (o instanceof Dog d) {
          formatted = "This is a dog";
      } else if (o instanceof Rat r) {
          formatted = "This is a rat";
      }
      return formatted;
  }

在 Java 17 中,我们可以这样重写上面的代码

static record Human (String name, int age, String profession) {}

public String checkObject(Object obj) {
    return switch (obj) {
        case Cat c && (c.size() > 100) -> "This is a big cat";
        case Dog d -> "This is a dog";
        case Rat r -> "This is a rat";
        case null -> "It is null";
        default -> "It is an animal";
    };
}

2.8 删除 RMI 激活 (JEP 407)

在版本 15 中已经标记为删除,此 JEP 在版本 17 中从平台中删除了 RMI 激活 API。

2.9 密封类 (JEP 409)

密封类是 Project Amber 的一部分,这个 JEP 正式为该语言引入了一项新功能,尽管它在 JDK 版本 15 和 16 中以预览模式提供。

该功能限制了哪些其他类或接口可以扩展或实现密封组件。展示与模式匹配相关的另一项改进,结合 JEP 406 将允许对类型、强制转换和行为代码模式进行更复杂和更清晰的检查。

让我们看看它的实际效果:

public sealed interface Log
    permits ClassLog, MethodLog, FieldLog {
    //...
}

2.10 删除实验性质的 AOT 和 JIT 编译器 (JEP 410)

GraalVM (JEP-317) 的 Ahead-Of-Time (AOT) 编译 (JEP 295) 和 Just-In-Time (JIT) 编译器分别作为实验性特性被引入到 JDK 9 和 JDK 10,但是维护成本过高。

另一方面,他们没有被大量采用。因此,这个 JEP 将它们从平台中移除,但开发人员仍然可以使用 GraalVM 来利用它们。

2.11 安全管理器(Security Manager)被标记为删除 (JEP 411)

旨在保护客户端 Java 代码的安全管理器是另一个标记为删除的功能,因为不再相关。

2.12 外部函数和内存 API(孵化器)(JEP 412)

Foreign Function and Memory API 允许 Java 开发人员从 JVM 外部访问代码并管理堆外的内存。目的是为了替换 JNI API 并与旧 API 相比提高安全性和性能。

这个 API 是 Project Panama 开发的另一个功能,它已由 JEP 393, 389, 383370 演变过来的。

使用此功能,我们可以从 Java 类调用 C 库:

private static final SymbolLookup libLookup;

static {
    // loads a particular C library
    var path = JEP412.class.getResource("/print_name.so").getPath();
    System.load(path);
    libLookup = SymbolLookup.loaderLookup();
}

首先,有必要通过 API 加载我们希望调用的目标库。

接下来,我们需要指定目标方法的签名并最终调用它:

public String getPrintNameFormat(String name) {

    var printMethod = libLookup.lookup("printName");

    if (printMethod.isPresent()) {
        var methodReference = CLinker.getInstance()
            .downcallHandle(
                printMethod.get(),
                MethodType.methodType(MemoryAddress.class, MemoryAddress.class),
                FunctionDescriptor.of(CLinker.C_POINTER, CLinker.C_POINTER)
            );

        try {
            var nativeString = CLinker.toCString(name, newImplicitScope());
            var invokeReturn = methodReference.invoke(nativeString.address());
            var memoryAddress = (MemoryAddress) invokeReturn;
            return CLinker.toJavaString(memoryAddress);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }
    throw new RuntimeException("printName function not found.");
}

2.13 Vector API(第二个孵化器)(JEP 414)

Vector API 处理 SIMD(单指令 Single Instruction, 多数据 Multiple Data)类型的操作,这意味着并行执行的各种指令集。它利用支持向量指令的专用 CPU 硬件,并允许执行流水线等指令。

因此,新的 API 将使开发人员能够实现更高效的代码,充分利用底层硬件的潜力。

此操作的日常使用场景包括科学代数线性应用程序、图像处理、字符处理以及任何繁重的算术应用程序或任何需要对多个独立操作数应用操作的应用程序。

让我们使用 API 来说明一个简单的向量乘法示例:

public void newVectorComputation(float[] a, float[] b, float[] c) {
    for (var i = 0; i < a.length; i += SPECIES.length()) {
        var m = SPECIES.indexInRange(i, a.length);
        var va = FloatVector.fromArray(SPECIES, a, i, m);
        var vb = FloatVector.fromArray(SPECIES, b, i, m);
        var vc = va.mul(vb);
        vc.intoArray(c, i, m);
    }
}

public void commonVectorComputation(float[] a, float[] b, float[] c) {
    for (var i = 0; i < a.length; i ++) {
        c[i] = a[i] * b[i];
    }
}

2.14 特定于上下文的反序列化过滤器 (JEP 415)

JEP 290 首次在 JDK 9 中引入,使我们能够验证来自不受信任来源的传入序列化数据,这是许多安全问题的常见来源。该验证发生在 JVM 级别,从而提供更高的安全性和稳健性。

使用 JEP 415,应用程序可以配置在 JVM 级别定义的特定于上下文,并且可以动态选择的反序列化过滤器。每个反序列化操作都会调用这样的过滤器。

这个 JEP 415 引入了过滤器工厂的概念,即 BinaryOperator,用于动态选择不同的反序列化过滤器或特定于上下文。 工厂决定如何组合两个过滤器或更换过滤器。

下面我们来看一个 JEP 中的例子

FilterInThread 类显示如何过滤当前线程中发生的每个反序列化操作。 它定义了一个线程局部变量来保存每个线程的过滤器,定义一个过滤器工厂来返回该过滤器,将工厂配置为 JVM 范围的过滤器工厂,并提供一个实用函数来在每个线程过滤器的上下文中运行 Runnable 。

public class FilterInThread implements BinaryOperator<ObjectInputFilter> {

    // ThreadLocal 用来存储串行过滤器
    private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();

    // 构造一个 FilterInThread 反序列化过滤器工厂.
    public FilterInThread() {}

    /**
     * 过滤器工厂,每次创建新的 ObjectInputStream 时都会调用它。 
     * 如果已经设置了每个流的过滤器,则它返回一个过滤器,该过滤器结合了调用每个过滤器的结果。
     *
     * @param curr 当前流的过滤器
     * @param next 每个流的过滤器
     * @return 一个过滤器
     */
    public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
        if (curr == null) {
            // 从 OIS 构造函数或者可能在没有当前过滤器的情况下从 OIS.setObjectInputFilter 调用
            var filter = filterThreadLocal.get();
            if (filter != null) {
                // 前置一个过滤器以断言所有类都已被允许或拒绝
                filter = ObjectInputFilter.rejectUndecidedClass(filter);
            }
            if (next != null) {
                // 将下一个过滤器添加到线程过滤器,如果最初有的话,这是从 OIS 构造函数传递的静态 JVM 范围过滤器,附加过滤器以拒绝所有未决定的结果
                filter = ObjectInputFilter.merge(next, filter);
                filter = ObjectInputFilter.rejectUndecidedClass(filter);
            }
            return filter;
        } else {
            // 使用当前过滤器和特定于流的过滤器从 OIS.setObjectInputFilter 调用。
            // curr 过滤器已经包含了线程过滤器和静态 JVM 范围的过滤器以及对未决定类的拒绝
            // 如果有一个特定于流的过滤器在它前面加上一个过滤器来重新检查未决定的
            if (next != null) {
                next = ObjectInputFilter.merge(next, curr);
                next = ObjectInputFilter.rejectUndecidedClass(next);
                return next;
            }
            return curr;
        }
    }

    /**
     * 应用过滤器并调用 runnable.
     *
     * @param filter 应用于线程中每个反序列化的串行过滤器
     * @param runnable 一个可调用的 Runnable
     */
    public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
        var prevFilter = filterThreadLocal.get();
        try {
            filterThreadLocal.set(filter);
            runnable.run();
        } finally {
            filterThreadLocal.set(prevFilter);
        }
    }
}

3. LTS 定义

这些变化不仅仅停留在代码中——流程也在发生变化。

Java 平台的发布历史悠久且不精确,这是众所周知的。尽管设计为在发布之间有三年的节奏,但它通常变成了一个四年的过程。

此外,鉴于市场的新动态,创新和快速响应变得势在必行,负责平台演进的团队决定改变发布节奏以适应新的情况。

因此,自 Java 10(2018 年 3 月 20 日发布)以来,采用了新的 6 个月功能发布模型。

3.1 六个月的功能发布模型

新的六个月功能发布模型允许平台开发人员在准备好时发布功能。这消除了发布新功能到新的版本的压力。否则,他们将不得不等待三到四年才能将新功能提供给平台的用户。

新模型还改善了用户和平台架构师之间的反馈周期。这是因为功能可以在孵化模式下提供,并且只有在多次交互后才发布以供一般使用。

3.2 LTS 模型

由于企业应用程序广泛使用了 Java,因此稳定性至关重要。此外,继续为所有这些版本提供支持和补丁更新的成本很高。

因此,创建了长期支持 (LTS) 版本,为用户提供扩展支持。因此,由于错误修复、性能改进和安全补丁,此类版本自然会变得更加稳定和安全。对于 Oracle,这种支持通常持续八年。

由于引入了发布模型的变化,LTS 版本为 Java SE 11(2018 年 9 月发布)和 Java SE 17(2021 年 9 月发布)。尽管如此,版本 17 为模型带来了一些新的东西。简而言之,LTS 版本之间的间隔现在是两年而不是三年,这使得 Java 21(计划于 2023 年 9 月发布)可能是下一个 LTS。

还有一点值得一提的是,这个发布模式并不新鲜。它是从其他项目中借鉴过来的,例如 Mozilla Firefox、Ubuntu 等,它们都已经证明了这个发布模式的实用性。

4. 新发布流程

新的发布流程是基于 JEP 3 ,它描述了流程中的所有更改。我们将尝试在这里提供一个简明的总结。

鉴于上述新模型,再加上平台的不断发展和新的六个月发布节奏(通常是六月和十二月),Java 将发展得更快。 JDK 的开发团队将按照下面描述的过程启动下一个功能版本的发布周期。

该过程从主线的 fork 开始。然后在稳定的代码库 JDK/JDK$N(例如 JDK17)中继续开发。在那里,开发继续关注发布的稳定性。

在我们深入研究这个过程之前,我们先来看一下一些术语:

  • Bugs:在这种情况下,错误意味着 tickets 或者 tasks:
    • Current:这些要么是与当前版本(即将发布的新版本)相关的实际的 bugs,要么是对已包含在此版本中的新功能的调整(新 JEP)。
    • Targeted:与旧版本相关并计划在此新版本中修复或解决
  • Priorities:从 P1 到 P5,其中 P1 最重要,重要性逐渐降低到 P5

4.1 新流程

稳定过程将在接下来的三个月进行:

  • JDK/JDK$N 代码库就像一个发布分支,此时,没有新 JEP 的新 JEP 进入存储库。
  • 接下来,该代码库中的开发将稳定并移植到主线。
  • 减速阶段 1 (Ramp Down Phase 1 - RDP 1):持续四到五周。开发人员放弃所有 Current P4-P5 和 targeted P1-P3(取决于 deferring, fixenhancement)。这意味着 P5+ 测试/文档 bugs 和 targeted P3+ 代码 bugs 是可选的。
  • 减速阶段 2 (RDP 2):持续三到四个星期。现在他们推迟所有 Current P3-P5 和 targeted P1-P3(取决于 deferring, fixenhancement)。
  • 最后,该团队发布了一个发布候选版本并将其提供给公众。此阶段持续两到五周,仅解决当前的 P1 修复(使用 fix)。

一旦所有这些周期完成,新版本将成为通用 (GA) 版本。

5.下一步是什么

JDK 架构师继续致力于许多旨在使平台现代化的项目。目标是提供更好的开发体验以及更健壮和高性能的 API。

另一个影响当前和未来版本的相关新闻是对于 Oracle JDK 发行版(或 Hotspot)的新的免费条款和条件许可。在大多数情况下,Oracle 为生产和其他环境免费提供其分发,但也有一些例外。再次,请参阅链接。

如前所述,新流程的目标是下一个 LTS 版本为 21 版,并计划在 2023 年 9 月之前发布。

6. 结论

在本文中,我们查看了有关 Java 17 版本的一些新功能,了解了它的最新发展、新功能、支持定义和发布周期过程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值