Skip to main content

jdk21新特性

2023年9月19日,JDK 21 正式发布,JDK 21 是长期维护的版本。包含了以下新特性

  1. 字符串模板(预览版) String Templates (Preview)
  2. 有序集合 Sequenced Collections
  3. 分代ZGC Generational ZGC
  4. 记录模式 Record Patterns
  5. switch的模式匹配 Pattern Matching for switch
  6. 外部函数和内存 API(第三次预览) Foreign Function & Memory API (Third Preview)
  7. 未命名模式和变量(预览版) Unnamed Patterns and Variables (Preview)
  8. 虚拟线程 Virtual Threads
  9. 未命名类和实例main方法(预览版) Unnamed Classes and Instance Main Methods (Preview)
  10. 范围值(预览版)Scoped Values (Preview)
  11. 向量 API(第六次孵化)Vector API (Sixth Incubator)
  12. 弃用 Windows 32 位 x86 端口为删除作准备 Deprecate the Windows 32-bit x86 Port for Removal
  13. 准备禁止动态加载代理 Prepare to Disallow the Dynamic Loading of Agents
  14. 密钥封装机制API Key Encapsulation Mechanism API
  15. 结构化并发(预览版)Structured Concurrency (Preview)

430 字符串模板(预览版)

字符串模板通过将 文本 与 嵌入式表达式 和 模板处理器 结合来生成专门的结果,主要就是更方便的处理字符串。 这是预览版功能,想要用起来会麻烦一些,需要在编译和运行时都加上 --enable-preview 选项。我们来看代码:(为了方便演示,我们使用jshell来查看结果)

$ jshell --enable-preview
jshell> var name = "tom";
|name ==> "tom"
jshell> var greet = STR."hi \{name}";
|greet ==> "hi tom"

上面的代码中,用到了 STR 关键字。在字符串中用 \{} 来表达模板。

我们来看更多的示例:

// 支持算术表达式, 加减乘除
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
// "10 + 20 = 30"

// 方法调用
String s = STR."现在时间:\{System.currentTimeMillis()}"
|s ==> "现在时间:1695520672393"

//使用成员变量
record Person(String name,int height){}
Person p = new Person("张三",170)
String s = STR."姓名:\{p.name()} 身高:\{p.height()}";
|s ==> "姓名:张三 身高:170"

从上面的示例可以看到 { 和 } 中间,是可以写java代码的。 再来看一个示例:

File   file     = new File("a.dat");
String msg = STR."文件 \{filePath} \{file.exists() ? "" : "不"} 存在";
| "文件 a.dat 存在" 或者 "文件 a.dat 不存在"

对于 多行文本,模板语法也是支持的。示例:

String name = "张三";
int height = 170;
String des = STR."""
姓名:\{name}
身高: \{height}
""";
| """
| 姓名:张三
| 身高: 170
| """

FMT 模板

除了 STR 模板,还有FMT模板。FMT 是 Java 平台中定义的另一个模板处理器。 FMT 与 STR 类似,它执行插值,但它也解释出现在嵌入表达式左侧的格式说明符。 格式说明符与 java.util.Formatter 中定义的格式说明符相同。 我们看一个示例:

double d = 1.234567;
String table = FMT."%3.2f\{d}";
| 1.23

需要注意的是,使用 FMT需要 import ,不然编译报错:

import static java.util.FormatProcessor.FMT;

431 有序集合

jdk 21 新增了一个接口 java.util.SequencedCollection 这个接口里包含了以下方法:

interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed(); // 反转
void addFirst(E); // 在前面添加
void addLast(E); // 在后面添加
E getFirst(); // 获取第一个元素
E getLast(); // 获取最后一个元素
E removeFirst(); // 删除第一个元素
E removeLast(); // 删除最后一个元素
}

以下JAVA类都实现了上述的接口。

  • List
  • Deque
  • LinkedHashSet
  • SortedSet
  • LinkedHashMap
  • SortedMap

从使用的角度来讲很简单,用到相关类的时候,看看有没有这些方法,就可以了。

  • 值得注意的是,这些接口的实现,有的接口是会抛异常的,实际使用时,需要特别小心,比如:
default E getFirst() {
if (this.isEmpty()) {
throw new NoSuchElementException();
} else {
return this.get(0);
}
}

我们看到,list里,这个 getFirst 方法,会判断list是否为空,如果为空的话 就会 抛出 NoSuchElementException 异常。

439 分代ZGC Generational ZGC

通过扩展 ZGC 来维护新旧对象的不同代,从而提高应用程序性能。 这将使 ZGC 能够更频繁地收集年轻对象(这些对象往往会在年轻时死亡)。 使用 Generational ZGC 运行的应用程序应该享受

  • 降低分配停滞的风险,
  • 降低所需的堆内存开销,并且
  • 降低垃圾收集 CPU 开销。
  • 与非分代 ZGC 相比,这些好处应该不会显着降低吞吐量。 应保留非代 ZGC 的基本属性:

暂停时间不应超过1毫秒,应支持从几百兆字节到数 TB 的堆大小,并且应该需要最少的手动配置。

一句话,就是优化了GC算法。

440 记录模式 Record Patterns

使用记录模式增强 Java 编程语言以解构记录值。 记录模式和类型模式可以嵌套,以实现强大的、声明性的、可组合形式的数据导航和处理。

// Java 16 的写法
record Point(int x, int y) {}

static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
// ava 21 的写法
record Point(int x, int y) {}

static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}

也就是可以在 instanceof 的时候,把成员变量也声明上,然后主可以直接使用。

441 switch的模式匹配

switch 模式匹配

在 Java 21 之前,我们需要这样写:

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

而到了 Java 21 ,我们可以这样写:

static String formatterPatternSwitch(Object obj) {
return switch (obj) {
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 -> obj.toString();
};
}

一下子就清爽了。

switch 支持 null

在 Java 21 之前,我们需要这样写:

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

而到了 Java 21 ,我们可以这样写:

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

442 外部函数和内存 API(第三次预览)

引入一个 API,Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。 主要是方便操作堆外内存,一般的开发基本都用不上。

443 未命名模式和变量(预览版)

当有一些变量我们不需要处理时,就可以使用未命名变量,未命名变更使用 _ 来替代变量名。比如:

String s = ...
try {
int i = Integer.parseInt(s);
... i ...
} catch (NumberFormatException _) {
System.out.println("Bad number: " + s);
}

444 虚拟线程

个人以为虚拟线程是java 21 最大的亮点,用于高并发。虚拟线程有3个目标:

  1. 让以简单的 一个请求一个线程风格 编写的服务器应用程序能够以接近最佳的硬件利用率进行扩展。
  2. 使用 java.lang.Thread API 的现有代码能够以最小的更改采用虚拟线程。
  3. 使用现有 JDK 工具轻松进行虚拟线程故障排除、调试和分析。

每一个java.lang.Thread 的实例对应一个操作系统线程,操作系统线程数量有限,具体取决于操作系统的实现。虚拟线程的限制就比操作系统线程的限制大多了。下面的代码示例了 100万虚拟线程的执行.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits

不要使用虚拟线程池,线程池是常用的,但用了虚拟线程,就不再用池了。语言本身已经处理过了,就没有必要再用池了。

查看虚拟线程,可以使用以下命令:

$ jcmd <pid> Thread.dump_to_file -format=json <file>

参考文档