jdk21新特性
2023年9月19日,JDK 21 正式发布,JDK 21 是长期维护的版本。包含了以下新特性
- 字符串模板(预览版) String Templates (Preview)
- 有序集合 Sequenced Collections
- 分代ZGC Generational ZGC
- 记录模式 Record Patterns
- switch的模式匹配 Pattern Matching for switch
- 外部函数和内存 API(第三次预览) Foreign Function & Memory API (Third Preview)
- 未命名模式和变量(预览版) Unnamed Patterns and Variables (Preview)
- 虚拟线程 Virtual Threads
- 未命名类和实例main方法(预览版) Unnamed Classes and Instance Main Methods (Preview)
- 范围值(预览版)Scoped Values (Preview)
- 向量 API(第六次孵化)Vector API (Sixth Incubator)
- 弃用 Windows 32 位 x86 端口为删除作准备 Deprecate the Windows 32-bit x86 Port for Removal
- 准备禁止动态加载代理 Prepare to Disallow the Dynamic Loading of Agents
- 密钥封装机制API Key Encapsulation Mechanism API
- 结构化并发(预览版)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个目标:
- 让以简单的 一个请求一个线程风格 编写的服务器应用程序能够以接近最佳的硬件利用率进行扩展。
- 使用 java.lang.Thread API 的现有代码能够以最小的更改采用虚拟线程。
- 使用现有 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>