JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了JDK17特性讲解

Posted 波波烤鸭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了JDK17特性讲解相关的知识,希望对你有一定的参考价值。

JDK各个版本特性讲解-JDK17特性

一、JAVA17概述

  JDK 16 刚发布半年(2021/03/16),JDK 17 又如期而至(2021/09/14),这个时间点特殊,蹭苹果发布会的热度?记得当年 JDK 15 的发布也是同天

Oracle 宣布,从 JDK 17 开始,后面的 JDK 都全部免费提供!!!

  Java 17+ 可以免费使用了,包括商用,更详细的条款可以阅读:

https://www.oracle.com/downloads/licenses/no-fee-license.html

  JDK 17 是自 2018 年 JDK 11 后的第二个长期支持版本,支持到 2029 年 9 月,支持时间长达 8 年,这下可以不用死守 JDK 8 了,JDK 17+ 也可以是一种新的选择了。下一个第三个长期支持版本是 JDK 21,时间为 2023 年 9 月,这次长期支持版本发布计划改了,不再是原来的 3 年一次,而是改成了 2 年一次!非长期支持版本还是半年发一次不变,下一个非长期支持版本计划在 2022/03 发布

OpenJDK文档:https://openjdk.java.net/projects/jdk/17/

  JDK 17 这个版本提供了 14 个增强功能,另外在性能、稳定性和安全性上面也得到了大量的提升,以及还有一些孵化和预览特性,有了这些新变化,Java 会进一步提高开发人员的生产力。

二、语法层面的变化

1.JEP 409:密封类

概述

  密封类,这个特性在 JDK 15 中首次成为预览特性,在 JDK 16 中进行二次预览,在 JDK 17 这个版本中终于正式转正了。

历史

  密封类是由JEP 360提出的,并在JDK 15 中作为 预览功能提供。它们由JEP 397再次提出并进行了改进,并作为预览功能在 JDK 16中提供。该 JEP 建议在 JDK 17 中完成密封类,与 JDK 16 没有任何变化。

目标

  • 允许类或接口的作者控制负责实现它的代码。
  • 提供比访问修饰符更具声明性的方式来限制超类的使用。
  • 通过为模式的详尽分析提供基础,支持模式匹配的未来方向。

原因

  类和接口的继承层次结构的面向对象数据模型已被证明在对现代应用程序处理的现实世界数据进行建模方面非常有效。这种表现力是 Java 语言的一个重要方面。

  然而,在某些情况下,可以有效地控制这种表现力。例如,Java 支持枚举类来模拟给定类只有固定数量实例的情况。在以下代码中,枚举类列出了一组固定的行星。它们是该类的唯一值,因此您可以彻底切换它们——无需编写 default子句:

enum Planet  MERCURY, VENUS, EARTH 

Planet p = ...
switch (p) 
  case MERCURY: ...
  case VENUS: ...
  case EARTH: ...

  使用枚举类值的模型固定集通常是有帮助的,但有时我们想一套固定的模型价值。我们可以通过使用类层次结构来做到这一点,而不是作为代码继承和重用的机制,而是作为列出各种值的一种方式。以我们的行星示例为基础,我们可以对天文领域中的各种值进行建模,如下所示:

interface Celestial  ... 
final class Planet implements Celestial  ... 
final class Star   implements Celestial  ... 
final class Comet  implements Celestial  ... 

  然而,这种层次结构并没有反映重要的领域知识,即我们的模型中只有三种天体。在这些情况下,限制子类或子接口的集合可以简化建模。

  考虑另一个例子:在一个图形库中,一个类的作者 Shape 可能希望只有特定的类可以扩展 Shape,因为该库的大部分工作涉及以适当的方式处理每种形状。作者对处理 的已知子类的代码的清晰度感兴趣 Shape,而对编写代码来防御 的未知子类不感兴趣 Shape。允许任意类扩展 Shape,从而继承其代码以供重用,在这种情况下不是目标。不幸的是,Java 假定代码重用始终是一个目标:如果 Shape可以扩展,那么它可以扩展任意数量的类。放宽这个假设是有帮助的,这样作者就可以声明一个类层次结构,该层次结构不能被任意类扩展。在这样一个封闭的类层次结构中,代码重用仍然是可能的,但不能超出。

  Java 开发人员熟悉限制子类集的想法,因为它经常出现在 API 设计中。该语言在这方面提供了有限的工具:要么创建一个类 final,使其具有零个子类,要么使该类或其构造函数成为包私有的,因此它只能在同一个包中具有子类。包私有超类的示例 出现在 JDK 中

package java.lang;

abstract class AbstractStringBuilder  ... 
public final class StringBuffer  extends AbstractStringBuilder  ... 
public final class StringBuilder extends AbstractStringBuilder  ... 

  当目标是代码重用时,包私有方法很有用,例如 AbstractStringBuilderappend. 然而,当目标是对替代方案进行建模时,这种方法是无用的,因为用户代码无法访问关键抽象——超类——以 switch覆盖它。允许用户访问超类而不允许他们扩展它是无法指定的,除非诉诸涉及非 public构造函数的脆弱技巧——这些技巧不适用于接口。在声明 Shape及其子类的图形库中,如果只有一个包可以访问 Shape.

  总之,超类应该可以被广泛访问 (因为它代表用户的重要抽象)但不能广泛 扩展(因为它的子类应该仅限于作者已知的那些)。这样一个超类的作者应该能够表达它是与一组给定的子类共同开发的,既为读者记录意图,又允许 Java 编译器强制执行。同时,超类不应过度约束其子类,例如,强迫它们成为 final或阻止它们定义自己的状态。

2.JEP 406:switch模式匹配(预览)

概述

  使用 switch 表达式和语句的模式匹配以及对模式语言的扩展来增强 Java 编程语言。扩展模式匹配以 switch允许针对多个模式测试表达式,每个模式都有特定的操作,以便可以简洁安全地表达复杂的面向数据的查询。

  instanceof 模式匹配是JAVA14 非常赞的一个新特性! 这次在 JDK 17 中为 switch 语句支持模式匹配

目标

  • switch通过允许模式出现在 case标签中来扩展表达式和语句的表现力和适用性。
  • 允许在 switch需要时放松对历史的零敌意。
  • 引入两种新的模式:保护模式,允许使用任意布尔表达式细化模式匹配逻辑,以及带 括号的模式,以解决一些解析歧义。
  • 确保所有现有的 switch表达式和语句继续编译而不做任何更改并以相同的语义执行。
  • 不要引入 switch与传统 switch 构造分离的具有模式匹配语义的新式表达式或语句。
  • switch当 case 标签是模式与 case 标签是传统常量时,不要使表达式或语句的行为不同。

老式的写法

static String formatter(Object o) 
    String formatted = "unknown";
    if (o instanceof Integer i) 
        formatted = String.format("int %d", i);
     else if (o instanceof Long l) 
        formatted = String.format("long %d", l);
     else if (o instanceof Double d) 
        formatted = String.format("double %f", d);
     else if (o instanceof String s) 
        formatted = String.format("String %s", s);
    
    return formatted;

支持模式匹配的switch

static String formatterPatternSwitch(Object o) 
    return switch (o) 
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    ;

  直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,使用模式匹配得到具体类型,大大简化了语法量,这个功能还是挺实用的, 目前看转正只是一个时间上的问题而已.

三、API层面变化

1.JEP 414:Vector API(第二个孵化器)

概括

  引入一个 API 来表达向量计算,这些计算在运行时可靠地编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。

历史

  Vector API 由JEP 338提出并作为孵化 API集成到 Java 16 中。我们在此建议结合改进以响应反馈以及性能改进和其他重要的实施改进。我们包括以下显着变化:

  • 增强 API 以支持字符操作,例如 UTF-8 字符解码。具体来说,我们添加了在 short向量和 char数组之间复制字符的方法,以及用于与整数向量进行无符号比较的新向量比较运算符。
  • 用于将 byte向量与 boolean 数组相互转换的 API 的增强功能。
  • 使用英特尔的短向量数学库 (SVML)对 x64 上的超越和三角泳道运算的内在支持。
  • Intel x64 和 ARM NEON 实现的一般性能增强。

目标

  • 清晰简洁的 API ——API 应该能够清晰简洁地表达广泛的向量计算,这些向量计算由循环内组成的向量操作序列组成,可能还有控制流。应该可以表达与向量大小或每个向量的车道数相关的通用计算,从而使此类计算能够跨支持不同向量大小的硬件进行移植。
  • 平台不可知——API应该是 CPU 架构不可知的,支持在支持向量指令的多种架构上实现。就像在 Java API 中一样,平台优化和可移植性发生冲突,那么偏向于使 API 可移植,即使这会导致某些特定于平台的习语无法在可移植代码中表达。
  • 在 x64 和 AArch64 架构上可靠的运行时编译和性能——在强大的 x64 架构上,Java 运行时,特别是 HotSpot C2 编译器,应该将向量操作编译为相应的高效和高性能向量指令,例如那些由Streaming SIMD Extensions (SSE) 和Advanced支持的 指令矢量扩展 (AVX)。开发人员应该相信他们表达的向量操作将可靠地映射到相关的向量指令。在功能强大的 ARM AArch64 架构上,C2 将类似地将向量操作编译为NEON支持的向量指令。
  • 优雅降级——有时向量计算无法在运行时完全表达为向量指令序列,这可能是因为架构不支持某些所需的指令。在这种情况下,Vector API 实现应该优雅地降级并仍然起作用。如果无法将向量计算有效地编译为向量指令,则这可能涉及发出警告。在没有向量的平台上,优雅降级将产生与手动展开循环竞争的代码,其中展开因子是所选向量中的通道数。

原因

  向量计算由对向量的一系列操作组成。向量包含(通常)固定的标量值序列,其中标量值对应于硬件定义的向量通道的数量。应用于具有相同通道数的两个向量的二元运算,对于每个通道,将对来自每个向量的相应两个标量值应用等效的标量运算。这通常称为单指令多数据(SIMD)。

  向量运算表达了一定程度的并行性,可以在单个 CPU 周期内执行更多工作,从而显着提高性能。例如,给定两个向量,每个向量包含八个整数的序列(即八个通道),可以使用单个硬件指令将这两个向量相加。向量加法指令对十六个整数进行运算,执行八次整数加法,而通常对两个整数进行运算,执行一次整数加法所需的时间。

  HotSpot 已经支持自动向量化,它将标量操作转换为超字操作,然后映射到向量指令。可转换的标量操作集是有限的,并且在代码形状的变化方面也很脆弱。此外,可能仅使用可用向量指令的子集,从而限制了生成代码的性能。

  今天,希望编写可靠地转换为超字操作的标量操作的开发人员需要了解 HotSpot 的自动矢量化算法及其局限性,以实现可靠和可持续的性能。在某些情况下,可能无法编写可转换的标量操作。例如,HotSpot 不会转换用于计算数组散列码的简单标量操作(因此是 Arrays::hashCode方法),也不能自动矢量化代码以按字典顺序比较两个数组(因此我们添加了用于字典比较的内在函数)。

  Vector API 旨在通过提供一种在 Java 中编写复杂矢量算法的方法来改善这种情况,使用现有的 HotSpot 自动矢量化器,但使用用户模型使矢量化更加可预测和健壮。手工编码的向量循环可以表达高性能算法,例如向量化 hashCode或专门的数组比较,自动向量化器可能永远不会优化这些算法。许多领域都可以从这个显式向量 API 中受益,包括机器学习、线性代数、密码学、金融和 JDK 本身的代码。

2.JEP 415:特定于上下文的反序列化过滤器

概括

  允许应用程序通过 JVM 范围的过滤器工厂配置特定于上下文和动态选择的反序列化过滤器,该工厂被调用以为每个单独的反序列化操作选择一个过滤器

原因

  反序列化不受信任的数据是一种固有的危险活动,因为传入数据流的内容决定了创建的对象、其字段的值以及它们之间的引用。在许多典型用途中,流中的字节是从未知、不受信任或未经身份验证的客户端接收的。通过仔细构建流,攻击者可以导致恶意执行任意类中的代码。如果对象构造具有改变状态或调用其他操作的副作用,那么这些操作可能会损害应用程序对象、库对象甚至 Java 运行时的完整性。禁用反序列化攻击的关键是防止任意类的实例被反序列化,从而防止直接或间接执行它们的方法。

  我们在 Java 9 中引入了反序列化过滤器 (JEP 290),使应用程序和库代码能够在反序列化之前验证传入的数据流。此类代码java.io.ObjectInputFilter在创建反序列化流(即 a java.io.ObjectInputStream)时提供验证逻辑作为 a 。

  依赖流的创建者来明确请求验证有几个限制。这种方法不能扩展,并且很难在代码发布后更新过滤器。它也不能对应用程序中第三方库执行的反序列化操作进行过滤。

  为了解决这些限制,JEP 290 还引入了一个 JVM 范围的反序列化过滤器,可以通过 API、系统属性或安全属性进行设置。此过滤器是*静态的,*因为它在启动时只指定一次。使用静态 JVM 范围过滤器的经验表明,它也有局限性,尤其是在具有库层和多个执行上下文的复杂应用程序中。对每个使用 JVM 范围的过滤器 ObjectInputStream要求过滤器覆盖应用程序中的每个执行上下文,因此过滤器通常会变得过于包容或过于严格。

  更好的方法是以不需要每个流创建者参与的方式配置每个流过滤器。

  为了保护 JVM 免受反序列化漏洞的影响,应用程序开发人员需要清楚地描述每个组件或库可以序列化或反序列化的对象。对于每个上下文和用例,开发人员应该构建并应用适当的过滤器。例如,如果应用程序使用特定库来反序列化特定对象群组,则可以在调用库时应用相关类的过滤器。创建类的允许列表并拒绝其他所有内容,可以防止流中未知或意外的对象。封装或其他自然应用程序或库分区边界可用于缩小允许或绝对不允许的对象集。

  应用程序的开发人员处于了解应用程序组件的结构和操作的最佳位置。此增强功能使应用程序开发人员能够构建过滤器并将其应用于每个反序列化操作。

具体操作

  如上所述,JEP 290 引入了 per-stream 反序列化过滤器和静态 JVM-wide 过滤器。每当 ObjectInputStream创建一个时,它的每个流过滤器都会被初始化为静态 JVM 范围的过滤器。如果需要,可以稍后将每个流过滤器更改为不同的过滤器。

  这里我们介绍一个可配置的 JVM 范围的过滤器工厂。每当 ObjectInputStream创建an 时 ,它的每个流过滤器都会初始化为通过调用静态 JVM 范围过滤器工厂返回的值。因此,这些过滤器是动态的特定上下文的,与单个静态 JVM 范围的反序列化过滤器不同。为了向后兼容,如果未设置过滤器工厂,则内置工厂将返回静态 JVM 范围的过滤器(如果已配置)。

  过滤器工厂用于 Java 运行时中的每个反序列化操作,无论是在应用程序代码、库代码中,还是在 JDK 本身的代码中。该工厂特定于应用程序,应考虑应用程序中的每个反序列化执行上下文。过滤器工厂从 ObjectInputStream构造函数调用,也从 ObjectInputStream.setObjectInputFilter. 参数是当前过滤器和新过滤器。从构造函数调用时,当前过滤器是 null,新过滤器是静态 JVM 范围的过滤器。工厂确定并返回流的初始过滤器。工厂可以使用其他特定于上下文的控件创建复合过滤器,或者只返回静态 JVM 范围的过滤器。如果 ObjectInputStream.setObjectInputFilter被调用,工厂被第二次调用,并使用第一次调用返回的过滤器和请求的新过滤器。工厂决定如何组合两个过滤器并返回过滤器,替换流上的过滤器。

  对于简单的情况,过滤器工厂可以为整个应用程序返回一个固定的过滤器。例如,这里有一个过滤器,它允许示例类,允许 java.base模块中的类,并拒绝所有其他类:

var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*")

  在具有多个执行上下文的应用程序中,过滤器工厂可以通过为每个上下文提供自定义过滤器来更好地保护各个上下文。构建流时,过滤器工厂可以根据当前线程本地状态、调用者的层次结构、库、模块和类加载器来识别执行上下文。此时,用于创建或选择过滤器的策略可以根据上下文选择特定过滤器或过滤器组合。

  如果存在多个过滤器,则可以合并它们的结果。组合过滤器的一种有用方法是,如果任何过滤器拒绝反序列化,则拒绝反序列化,如果任何过滤器允许,则允许反序列化,否则保持未定状态。

命令行使用

属性 jdk.serialFilterjdk.serialFilterFactory可以
在命令行上设置过滤器和过滤器工厂。现有 jdk.serialFilter属性设置基于模式的过滤器。

jdk.serialFilterFactory属性是在第一次反序列化之前要设置的过滤器工厂的类名。该类必须是公共的,并且可由应用程序类加载器访问。

  为了与 JEP 290 兼容,如果 jdk.serialFilterFactory未设置属性,则过滤器工厂将设置为提供与早期版本兼容的内置函数。

应用程序接口

  我们在 ObjectInputFilter.Config类中定义了两个方法来设置和获取 JVM 范围的过滤器工厂。过滤器工厂是一个有两个参数的函数,一个当前过滤器和一个下一个过滤器,它返回一个过滤器。

/**
 * Return the JVM-wide deserialization filter factory.
 *
 * @return the JVM-wide serialization filter factory; non-null
 */
public static BinaryOperator<ObjectInputFilter> getSerialFilterFactory();

/**
 * Set the JVM-wide deserialization filter factory.
 *
 * The filter factory is a function of two parameters, the current filter
 * and the next filter, that returns the filter to be used for the stream.
 *
 * @param filterFactory the serialization filter factory to set as the
 * JVM-wide filter factory; not null
 */
public static void setSerialFilterFactory(BinaryOperator<ObjectInputFilter> filterFactory);

示例

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

public class FilterInThread implements BinaryOperator<ObjectInputFilter> 

    // ThreadLocal to hold the serial filter to be applied
    private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();

    // Construct a FilterInThread deserialization filter factory.
    public FilterInThread() 

    /**
     * The filter factory, which is invoked every time a new ObjectInputStream
     * is created.  If a per-stream filter is already set then it returns a
     * filter that combines the results of invoking each filter.
     *
     * @param curr the current filter on the stream
     * @param next a per stream filter
     * @return the selected filter
     */
    public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) 
        if (curr == null) 
            // Called from the OIS constructor or perhaps OIS.setObjectInputFilter with no current filter
            var filter = filterThreadLocal.get();
            if (filter != null) 
                // Prepend a filter to assert that all classes have been Allowed or Rejected
                filter = ObjectInputFilter.Config.rejectUndecidedClass(filter);
            
            if (next != null) 
                // Prepend the next filter to the thread filter, if any
                // Initially this is the static JVM-wide filter passed from the OIS constructor
                // Append the filter to reject all UNDECIDED results
                filter = ObjectInputFilter.Config.merge(next, filter);
                filter = ObjectInputFilter.Config.rejectUndecidedClass(filter);
            
            return filter;
         else 
            // Called from OIS.setObjectInputFilter with a current filter and a stream-specific filter.
            // The curr filter already incorporates the thread filter and static JVM-wide filter
            // and rejection of undecided classes
            // If there is a stream-specific filter prepend it and a filter to recheck for undecided
            if (next != null) 
                next = ObjectInputFilter.Config.merge(next, curr);
                next = ObjectInputFilter.Config.rejectUndecidedClass(next);
                return next;
            
            return curr;
        
    

    /**
     * Apply the filter and invoke the runnable.
     *
     * @param filter the serial filter to apply to every deserialization in the thread
     * @param runnable a Runnable to invoke
     */
    public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) 
        var prevFilter = filterThreadLocal.get();
        try 
            filterThreadLocal.set(filter);
            runnable.run();
         finally 
            filterThreadLocal.set(prevFilter);
        
    

  如果已经设置了特定于流的过滤器, ObjectInputStream::setObjectFilter则过滤器工厂将该过滤器与下一个过滤器组合。如果任一过滤器拒绝一个类,则该类将被拒绝。如果任一过滤器允许该类,则该类被允许。否则,结果未定。

  这是使用 FilterInThread该类的一个简单示例:

// Create a FilterInThread filter factory and set
    var filterInThread = new FilterInThread();
    ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);

    // Create a filter to allow example.* classes and reject all others
    var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*");
    filterInThread.doWithSerialFilter(filter, () -> 
          byte[] bytes = ...;
          var o = deserializeObject(bytes);
    );

四、其他变化

1.JEP 306:恢复始终严格的浮点语义

概括

  使浮点运算始终严格,而不是同时具有严格的浮点语义 ( strictfp) 和略有不同的默认浮点语义。这将恢复语言和 VM 的原始浮点语义,匹配 Java SE 1.2 中引入严格和默认浮点模式之前的语义。

目标

  • 简化数字敏感库的开发,包括 java.lang.Mathjava.lang.StrictMath.
  • 在平台的一个棘手方面提供更多的规律性。

原因

  1990 年代后期改变平台默认浮点语义的动力源于原始 Java 语言和 JVM 语义之间的不良交互以及流行的 x86 架构的 x87 浮点协处理器指令集的一些不幸特性. 在所有情况下匹配精确的浮点语义,包括非正规操作数和结果,需要大量额外指令的开销。在没有上溢或下溢的情况下匹配结果可以用更少的开销来实现,这大致是 Java SE 1.2 中引入的修改后的默认浮点语义所允许的。

  但是,从 2001 年左右开始在奔腾 4 和更高版本的处理器中提供的 SSE2(流式 SIMD 扩展 2)扩展可以以直接的方式支持严格的 JVM 浮点运算,而不会产生过多的开销。

  由于英特尔和 AMD 长期以来都支持 SSE2 和更高版本的扩展,这些扩展允许自然支持严格的浮点语义,因此不再存在具有不同于严格的默认浮点语义的技术动机。

具体细节描述

  此 JEP 将修改的接口包括浮点表达式覆盖范围内的 Java 语言规范(请参阅JLS部分 4.2.3浮点类型、格式和值、5.1.13 值集转换、15.4 FP-strict Expressions、对第 15 章后面其他部分的许多小更新)和 Java 虚拟机规范的类似部分(JVMS 2.3.2浮点类型、值集和值*,第 2.8.2 节浮点模式,2.8.3值集)转换,以及对个别浮点指令的许多小更新)。值集和值集转换的概念将从 JLS 和 JVMS 中删除。JDK 中的实现更改将包括更新 HotSpot 虚拟机,使其永远不会在允许扩展指数值集的浮点模式下运行(这种模式必须存在于 strictfp操作中)并更新 javac以发出新的 lint 警告以防止不必要的使用的 strictfp修饰符。

2.JEP 356:增强型伪随机数生成器

概括

  为伪随机数生成器 (PRNG) 提供新的接口类型和实现,包括可跳转的 PRNG 和额外的一类可拆分 PRNG 算法 (LXM)。

目标

  • 使在应用程序中交替使用各种 PRNG 算法变得更容易。
  • 通过提供 PRNG 对象流更好地支持基于流的编程。
  • 消除现有 PRNG 类中的代码重复。
  • 小心地保留 class 的现有行为 java.util.Random

原因

  • 使用遗留的 PRNG 类 RandomThreadLocalRandomSplittableRandom,很难用其他算法替换应用程序中的任何一个,尽管它们都支持几乎相同的方法集。例如,如果一个应用程序使用 class 的实例 Random,它必然会声明 type 的变量 Random,它不能保存 class 的实例 SplittableRandom;更改要使用的应用程序 SplittableRandom需要更改用于保存 PRNG 对象的每个变量(包括方法参数)的类型。一个例外是它 ThreadLocalRandom是 的子类 Random,纯粹是为了允许 类型的变量 Random保存 的实例 ThreadLocalRandom,但 ThreadLocalRandom几乎覆盖了 的所有方法 Random。接口可以轻松解决这个问题。
  • 传统类 RandomThreadLocalRandom以及 SplittableRandom所有支持等方法,nextDouble()nextBoolean()以及流产生方法,如 ints()longs(),但他们拥有完全独立的,几乎复制和粘贴的方式工作。重构此代码将使其更易于维护,此外,文档将使第三方更容易创建新的 PRNG 类,这些类也支持相同的完整方法套件。
  • 2016 年,测试揭示了 class 使用的算法中的两个新弱点 SplittableRandom。一方面,相对较小的修订可以避免这些弱点。另一方面,还发现了一类新的可拆分 PRNG 算法 (LXM),其速度几乎一样快,甚至更容易实现,并且似乎完全避免了容易出现的三类弱点 SplittableRandom
  • 能够从 PRNG 获取 PRNG 对象流使得使用流方法表达某些类型的代码变得更加容易。
  • 文献中有许多 PRNG 算法不是可拆分的,而是可跳跃的(也许也是可跳跃的,即能够进行非常长的跳跃和普通跳跃),这一特性与拆分完全不同,但也有助于支持流PRNG 对象。过去,很难在 Java 中利用这一特性。可跳转 PRNG 算法的示例是 Xoshiro256** 和 Xoroshiro128+。
    • Xoshiro256** 和 Xoroshiro128+:http 😕/xoshiro.di.unimi.it

具体描述

  我们提供了一个新接口,RandomGenerator为所有现有的和新的 PRNG 提供统一的 API。RandomGenerators提供命名方法 intslongsdoublesnextBooleannextIntnextLongnextDouble,和 nextFloat,与他们所有的当前参数的变化。

  我们提供了四个新的专门的 RandomGenerator 接口:

  • SplittableRandomGenerator扩展 RandomGenerator并提供
    名为 splitand 的方法 splits。可拆分性允许用户从现有的 RandomGenerator 生成一个新的 RandomGenerator,这通常会产生统计上独立的结果。
  • JumpableRandomGenerator扩展 RandomGenerator并提供
    名为 jumpand 的方法 jumps。可跳跃性允许用户跳过中等数量的平局。
  • LeapableRandomGenerator扩展 RandomGenerator并提供
    名为 leapand 的方法 leaps。Leapability 允许用户跳过大量的抽奖。
  • ArbitrarilyJumpableRandomGenerator扩展 LeapableRandomGenerator并且还提供了 jump和 的其他变体 jumps,允许指定任意跳跃距离。

  我们提供了一个新类 RandomGeneratorFactory,用于定位和构造 RandomGenerator实现的实例。在 RandomGeneratorFactory使用 ServiceLoader.Provider注册API RandomGenerator的实现。

  我们重构了 Random, ThreadLocalRandom,SplittableRandom以便共享它们的大部分实现代码,此外,还使这些代码也可以被其他算法重用。这个重构创建底层非公抽象类 AbstractRandomGeneratorAbstractSplittableRandomGeneratorAbstractJumpableRandomGeneratorAbstractLeapableRandomGenerator,和 AbstractArbitrarilyJumpableRandomGenerator,为每个方法提供仅实现 nextInt()nextLong()和(如果相关的话)任一 split(),或 jump(),或 jump()leap(),或 jump(distance)。经过这次重构,Random, ThreadLocalRandom, 和 SplittableRandom继承了 RandomGenerator接口。注意,因为 SecureRandom是 的子类 Random,所有的实例 SecureRandom也自动支持该 RandomGenerator接口,无需重新编码 SecureRandom 类或其任何相关的实现引擎。

  我们还添加了底层非公共类,这些类扩展 AbstractSplittableRandomGenerator (并因此实现 SplittableRandomGeneratorRandomGenerator)以支持 LXM 系列 PRNG 算法的六个特定成员:

  • L32X64MixRandom
  • L32X64StarStarRandom
  • L64X128MixRandom
  • L64X128StarStarRandom
  • L64X256MixRandom
  • L64X1024MixRandom
  • L128X128MixRandom
  • L128X256MixRandom
  • L128X1024MixRandom

  LXM 算法的中心 nextLong(或 nextInt)方法的结构遵循 Sebastiano Vigna 在 2017 年 12 月提出的建议,即使用一个 LCG 子生成器和一个基于异或的子生成器(而不是两个 LCG 子生成器)将提供更长的周期、优越的等分布、可扩展性和更好的质量。这里的每个具体实现都结合了目前最著名的基于异或的生成器之一(xoroshiro 或 xoshiro,由 Blackman 和 Vigna 在“Scrambled Linear Pseudorandom Number Generators”,ACM Trans. Math. Softw.,2021 中描述)与一个 LCG使用目前最著名的乘数之一(通过 Steele 和 Vigna 在 2019 年搜索更好的乘数找到),然后应用 Doug Lea 确定的混合函数。

我们还提供了这些广泛使用的 PRNG 算法的实现:

  • Xoshiro256PlusPlus
  • Xoroshiro128PlusPlus

  上面提到的非公共抽象实现将来可能会作为随机数实现器 SPI 的一部分提供。

  这套算法为 Java 程序员提供了空间、时间、质量和与其他语言兼容性之间的合理范围的权衡。

3.JEP 382:新的 macOS 渲染管线

概括

  使用 Apple Metal API 为 macOS 实现 Java 2D 内部渲染管道,作为现有管道的替代方案,现有管道使用已弃用的 Apple OpenGL API。

目标

  • 为使用 macOS Metal 框架的 Java 2D API 提供功能齐全的渲染管道。
  • 如果 Apple 从未来版本的 macOS 中删除已弃用的 OpenGL API,请做好准备。
  • 确保新管道到 Java 应用程序的透明度。
  • 确保实现与现有 OpenGL 管道的功能奇偶校验。
  • 在选定的实际应用程序和基准测试中提供与 OpenGL 管道一样好或更好的性能。
  • 创建适合现有 Java 2D 管道模型的干净架构。
  • 与 OpenGL 管道共存,直到它过时。

原因

两个主要因素促使在 macOS 上引入新的基于 Metal 的渲染管道:

具体描述

  大多数图形 Java 应用程序是使用 Swing UI 工具包编写的,该工具包通过 Java 2D API 呈现。在内部,Java 2D 可以使用软件渲染和屏幕上的 blit,也可以使用特定于平台的 API,例如 Linux 上的 X11/Xrender、Windows 上的 Direct3D 或 macOS 上的 OpenGL。这些特定于平台的 API 通常提供比软件渲染更好的性能,并且通常会减轻 CPU 的负担。Metal 是用于此类渲染的新 macOS 平台 API,取代了已弃用的 OpenGL API。(该名称与 Swing “金属”外观和感觉无关;这只是巧合。)

  我们创建了大量新的内部实现代码来使用 Metal 框架,就像我们已经为其他特定于平台的 API 所做的那样。虽然很容易适应现有框架,但新代码在使用图形硬件方面更加现代,使用着色器而不是固定功能管道。这些更改仅限于特定于 macOS 的代码,甚至只更新了 Metal 和 OpenGL 之间共享的最少量代码。我们没有引入任何新的 Java API,也没有改变任何现有的 API。

  Metal 管道可以与 OpenGL 管道共存。当图形应用程序启动时,会选择其中一个。目前,OpenGL 仍然是默认设置。仅当在启动时指定或 OpenGL 初始化失败时才使用 Metal,就像在没有 OpenGL 支持的未来版本的 macOS 中一样。

  在集成此 JEP 时,Apple 尚未删除 OpenGL。在此之前,应用程序可以通过 -Dsun.java2d.metal=truejava命令行上指定来选择加入 Metal 。我们将在未来的版本中将 Metal 渲染管线设为默认。

在集成到 JDK 之前,我们在Project Lanai 中对这个 JEP 进行了工作。

4.JEP 391:macOS/AArch64 端口

概括

将 JDK 移植到 macOS/AArch64。

原因

  Apple 宣布了一项将其 Macintosh 计算机系列从 x64 过渡到 AArch64的长期计划。因此,我们希望看到对 JDK 的 macOS/AArch64 端口的广泛需求。

  尽管可以通过 macOS 的内置Rosetta 2 转换器在基于 AArch64 的系统上运行 JDK 的 macOS/x64 版本,但该翻译几乎肯定会带来显着的性能损失。

具体描述

  Linux 的 AArch64 端口(JEP 237)已经存在,Windows 的 AArch64 端口(JEP 388)的工作正在进行中。我们希望通过使用条件编译(在 JDK 的端口中很常见)来重用来自这些端口的现有 AArch64 代码,以适应低级约定的差异,例如应用程序二进制接口 (ABI) 和保留的处理器寄存器集。

  macOS/AArch64 禁止内存段同时可执行和可写,这一策略称为write-xor-execute (W^X)。HotSpot VM 会定期创建和修改可执行代码,因此此 JEP 将在 HotSpot 中为 macOS/AArch64 实现 W^X 支持。

5.JEP 398:弃用 Applet API 以进行删除

概述

  弃用 Applet API 以进行删除。它基本上无关紧要,因为所有 Web 浏览器供应商都已取消对 Java 浏览器插件的支持或宣布了这样做的计划。 Java 9 中的JEP 289先前已弃用 Applet API,但并未将其删除。

具体内容

弃用或移除标准 Java API 的这些类和接口:

  • java.applet.Applet
  • java.applet.AppletStub
  • java.applet.AppletContext
  • java.applet.AudioClip
  • javax.swing.JApplet
  • java.beans.AppletInitializer

弃用(删除)引用上述类和接口的任何 API 元素,包括以下中的方法和字段:

  • java.beans.Beans
  • javax.swing.RepaintManager
  • javax.naming.Context

6.JEP 403:强封装 JDK 内部

概述

  强烈封装 JDK 的所有内部元素,除了 关键的内部 API,如 sun.misc.Unsafe. 不再可能通过单个命令行选项来放松内部元素的强封装,就像在 JDK 9 到 JDK 16 中那样。

  这个 JEP 是JEP 396的继承者,它将 JDK 从默认的宽松强封装转换为默认 强封装,同时允许用户根据需要返回到轻松的姿势。本 JEP 的目标、非目标、动机、风险和假设部分与 JEP 396 的部分基本相同,但为了读者的方便在此处复制。

目标

  • 继续提高 JDK 的安全性和可维护性,这是Project Jigsaw的主要目标之一。
  • 鼓励开发人员从使用内部元素迁移到使用标准 API,以便他们和他们的用户可以轻松升级到未来的 Java 版本。

原因

  多年来,各种库、框架、工具和应用程序的开发人员以损害安全性和可维护性的方式使用 JDK 的内部元素。特别是: