Bootstrap

【Java】Java 17 新特性概览

1. Java 17 简介

Java 17 于 2021年09月14日 正式发布,且为长期支持版本(Long-Term-Support - LTS)。

Spring 6.x 和 Spring Boot 3.x 最低支持的 Java 17,这里敲黑板:Java 17 将是继 Java 8 以来最重要的长期支持版本

下面为 Oracle Java SE 产品的一些关键产品日期示例(Oracle Java SE Product Releases):

在这里插入图片描述

2. Java 17 新特性

类型推断 - 新的 var 关键字

从 Java 10 开始,引入了局部变量类型推断(Local Variable Type Inference)功能,它可以让我们使用关键字 var 声明局部变量(而不是实际类型),而由编译器根据变量初始化的值自动推断出类型。

// Java 8 传统变量声明方法
String str = "pointer";

// Java 10 使用类型推断的变量声明方法
var str = "pointer";

Java 11 允许在 lambda 表达式中使用 var 进行参数声明:

public class VarInLambdaExample {
    public static void main(String[] args) {
        IntStream.of(1, 2, 3, 5, 6, 7)
            .filter((var i) -> i % 2 == 0)
            .forEach(System.out::println);
    }
}

垃圾回收器改进

从 Java 9 开始,G1默认的垃圾收集器。与 Parallel GC 相比,它减少了暂停时间,尽管它的总体吞吐量可能较低。G1 的更新包括将未使用的已提交内存返回给操作系统的能力(JEP 346)。

ZGC 垃圾收集器已在 Java 11 中引入,并已在 Java 15 (JEP 377) 中达到产品状态。它旨在进一步减少停顿。从 Java 13 开始,它也可以将未使用的已提交内存返回给操作系统 (JEP 351)。

JDK 14 中引入了 Shenandoah GC,并在 Java 15 ( JEP 379) 中达到了产品状态。它旨在保持低暂停时间并独立于堆大小。

在 Java 8 中如果您没有手动更改 GC,您仍然使用 Parallel GC。简单地切换到 Java 17 可能会使您的应用程序运行得更快,并具有更一致的方法运行时间。切换到 ZGC 或 Shenandoah 可能会得到更好的结果。

最后,No-Op Garbage Collector(JEP 318),尽管它是一个实验性功能。这个垃圾收集器实际上不做任何工作,因此允许您精确测量应用程序的内存使用情况。如果您想保持尽可能低的内存操作吞吐量,则很有用。

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

JDK 17 之前,我们可以借助 Random、ThreadLocalRandom 和SplittableRandom 来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。

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

PRNGopen in new window 用来生成接近于绝对随机数序列的数字序列。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。

(1)提供了一个新接口 RandomGenerator

它为所有现有的和新的 PRNG 提供了一个统一的 API。 RandomGenerators 提供名为 ints、longs、doubles、nextBoolean、nextInt、nextLong、nextDouble 和 nextFloat 的方法,以及它们当前的所有参数的变化。从而更容易在应用程序中互换使用各种 PRNG 算法。例如:

public class RandomTest {

    public static void main(String[] args) {
        testRandomGenerator(new Random());
        testRandomGenerator(new SplittableRandom());
        testRandomGenerator(ThreadLocalRandom.current());
    }

    static void testRandomGenerator(RandomGenerator randomGenerator) {
        IntStream ints = randomGenerator.ints(50, 0, 10);
        int[] randoms = ints.toArray();
        for (int i : randoms) {
            System.out.println(i);
        }
        System.out.println("random count = " + randoms.length);
    }
}

(2)提供了一个新类 RandomGeneratorFactory

它用于定位和构造 RandomGenerator 实现的实例:

public class RandomTest {

    public static void main(String[] args) {
        testRandomGeneratorFactory("Random");
        testRandomGeneratorFactory("L128X128MixRandom");
        testRandomGeneratorFactory("Xoshiro256PlusPlus");
    }

    static void testRandomGeneratorFactory(String randomGeneratorName) {
        RandomGeneratorFactory<RandomGenerator> factory = RandomGeneratorFactory.of(randomGeneratorName);
        // 使用时间戳作为随机数种子
        RandomGenerator randomGenerator = factory.create(System.currentTimeMillis());

        // 生成随机数
        val nextInt = randomGenerator.nextInt(10);
        System.out.println(nextInt);
    }
}

上述方法还支持如下LXM 系列 PRNG 算法:

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

以及广泛使用的 PRNG 算法:

  • Xoshiro256PlusPlus

  • Xoroshiro128PlusPlus

(3)提供了四个新的专用 RandomGenerator 接口

  • SplittableRandomGenerator 扩展了 RandomGenerator 并且还提供名为 split 和 splits 的方法。 可拆分性允许用户从现有的 RandomGenerator 生成一个新的 RandomGenerator,这通常会产生统计上独立的结果。

  • JumpableRandomGenerator 扩展了RandomGenerator 并且还提供名为 jump 和 jumps 的方法。 可跳跃性允许用户跳到中等数量的抽签。

  • LeapableRandomGenerator 扩展了 RandomGenerator 并且还提供方法名为leap和leaps的方法。 可跳跃性允许用户跳过大量的抽签。

  • ArbitrarilyJumpableRandomGenerator 扩展了 LeapableRandomGenerator 并且还提供了jump 和 jumps 的方法额外的变体,允许指定任意跳跃距离。

JEP 356: Enhanced Pseudo-Random Number Generators

JEP 398 删除弃用的 Applet API

Applet API 在 Java 17 进行删除。

Applet API 用于编写在 Web 浏览器端运行的 Java 小程序, 在 Java 9 时被标记弃用,但没有删除。

JEP 398: Deprecate the Applet API for Removal

JEP 406 - switch 表达式增强

Java 16 中,JEP 394 扩展了 instanceof 操作符以获取类型模式并执行类型匹配。这种适度的扩展可以简化熟悉的 instanceof-and-cast 习惯用法:

// Old code
if (o instanceof String) {
    String s = (String)o;
    ... use s ...
}

// New code
if (o instanceof String s) {
    ... use s ...
}

Java 17 中对 switch 表达式进行了增强,其中包括对 switch 表达式的模式匹配进行了优化。这些改进可以让开发人员更方便地使用 switch 表达式来进行条件判断和分支控制。

// Old code
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;
}

// New code
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();
    };
}

对于 null 值的判断也进行了优化:

// Old code
static void testFooBar(String s) {
    if (s == null) {
        System.out.println("oops!");
        return;
    }
    switch (s) {
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

// New code
static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

JEP 406: Pattern Matching for switch (Preview)

JEP 407 - 删除远程方法调用激活机制

删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。RMI 激活机制已过时且不再使用。

JEP 407: Remove RMI Activation

JEP 409 - 密封类(Sealed Classes)

密封类(Sealed Classes)由 JEP 360 提出,并作为预览功能在 JDK 15 中提供。它们由 JEP 397 再次提出并进行了改进,并作为预览功能在 JDK 16 中提供 。此 JEP 建议在 JDK 17 中最终确定密封类,与 JDK 16 相比没有任何更改。

没有密封类之前,在 Java 中如果想让一个类不能被继承和修改,我们可以使用 final 关键字对类进行修饰。不过,这种方式不太灵活,直接把一个类的继承和修改渠道给堵死了。

密封类可以对继承或者实现它们的类进行限制,这样这个类就只能被指定的类继承

// 抽象类 Shape 只允许 Circle 和 Circle 继承。
public sealed class Shape permits Circle, Circle {
    // Shape 类的定义
}

public final class Circle extends Shape {
    // Circle 类的定义
}

public final class Rectangle extends Shape {
    // Rectangle 类的定义
}

另外,任何扩展密封类的类本身都必须声明为 sealednon-sealedfinal

public final class Circle extends Shape {
}

public non-sealed class Rectangle extends Shape {
}

JEP 409: Sealed Classes

JEP 410 - 删除实验性的 AOT 和 JIT 编译器

在 Java 9 的 JEP 295 引入了实验性的提前 (AOT) 编译器,在启动虚拟机之前将 Java 类编译为本机代码。

在 Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。

JEP 410: Remove the Experimental AOT and JIT Compiler

JEP 411 - 删除弃用的安全管理器

安全管理器可追溯到 Java 1.0,多年来,它一直不是保护客户端 Java 代码的主要方法,也很少用于保护服务器端代码。为了推动 Java 向前发展,Java 17 删除了弃用的安全管理器。

JEP 411: Deprecate the Security Manager for Removal

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

Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。

下面是 FFM API 使用示例,这段代码获取了 C 库函数的 radixsort 方法句柄,然后使用它对 Java 数组中的四个字符串进行排序。

// 1. 在C库路径上查找外部函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker.downcallHandle(
                             stdlib.lookup("radixsort"), ...);
// 2. 分配堆上内存以存储四个字符串
String[] javaStrings   = { "mouse", "cat", "dog", "car" };
// 3. 分配堆外内存以存储四个指针
SegmentAllocator allocator = implicitAllocator();
MemorySegment offHeap  = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 4. 将字符串从堆上复制到堆外
for (int i = 0; i < javaStrings.length; i++) {
    // 在堆外分配一个字符串,然后存储指向它的指针
    MemorySegment cString = allocator.allocateUtf8String(javaStrings[i]);
    offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 5. 通过调用外部函数对堆外数据进行排序
radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');
// 6. 将(重新排序的)字符串从堆外复制到堆上
for (int i = 0; i < javaStrings.length; i++) {
    MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
    javaStrings[i] = cStringPtr.getUtf8String(0);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"});  // true

JEP 412: Foreign Function & Memory API (Incubator)

JEP 414 - 向量(Vector) API(第二次孵化)

Vector API 最初在 Java 16 孵化并集成,在 Java 17 中进行第二次孵化。这个特性可以让开发人员更方便地使用向量操作来进行数据处理。Vector API 可以提供更高的并行性和更好的性能,从而加速数据处理过程。

// 创建一个 Vector
Vector<Float> v = Vector.of(1.0f, 2.0f, 3.0f, 4.0f);

// 对 Vector 中的元素进行操作
Vector<Float> result = v.map(x -> x * 2).add(Vector.of(1.0f, 1.0f, 1.0f, 1.0f));

// 输出结果
System.out.println(result);

JEP 414: Vector API (Second Incubator)

更多新特性请参考:

JDK 17 – New Features in Java 17

https://openjdk.org/projects/jdk/17/

3. Java 17 升级实战

解决GC毛刺问题——转转搜索推荐服务JDK17升级实践

;