Java 21新特性
官方文档链接:https://openjdk.org/projects/jdk/21/
下载链接:https://www.oracle.com/cn/java/technologies/downloads/#jdk21-windows
1、介绍
JDK21 是2023.09.19发布的正式版
其他版本的含义:
- Alpha:软件或系统的内部测试版本,仅内部人员使用。一般不向外部发布,通常会有很多 Bug,除非你也是测试人员,否则不建议使用,alpha 就是 α,是希腊字母的第一位,表示最初级的版本,beta 就是 β,alpha 版就是比 beta 还早的测试版,一般都是内部测试的版本。
- Beta:公开测试版。β 是希腊字母的第二个,顾名思义,这一版本通常是在 Alpha 版本后,该版本相对于 Alpha 版已有了很大的改进,消除了严重的错误,但还是存在着一缺陷,需要经过多次测试来进一步消除。这个阶段的版本会一直加入新的功能。
- Gamma: 软件或系统接近于成熟的版本,只需要做一些小的改进就能发行。是 beta 版做过一些修改,成为正式发布的候选版本。
- RC:Release Candidate,发行候选版本。和 Beta 版最大的差别在于 Beta 阶段会一直加入新的功能,但是到了 RC 版本,几乎就不会加入新的功能了,而主要着重于除错。RC 版本是最终发放给用户的最接近正式版的版本,发行后改正 bug 就是正式版了,就是正式版之前的最后一个测试版。
- GA:General Available,正式发布的版本,这个版本就是正式的版本。在国外都是用 GA 来说明 release 版本的。比如:MySQL Community Server 5.7.21 GA 这是 MySQL Community Server 5.7 第 21 个发行稳定的版本,GA 意味着 General Available,也就是官方开始推荐广泛使用了。
- Release:这个版本通常就是所谓的“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本,该版本有时也称为标准版。一般情况下,Release 不会以单词形式出现在软件封面上,取而代之的是符号(R)。
- Stable:稳定版。在开源软件中,都有 stable 版,这个就是开源软件的最终发行版,用户可以放心大胆的用了。这一版本基于 Beta 版,已知 Bug 都被修复,一般情况下,更新比较慢。
2、新功能
根据官方文档:https://www.infoq.com/news/2023/09/java-21-so-far/
最终的 15 个新功能集(以JEP的形式)可分为四 (4) 类:核心 Java 库、Java 语言规范、热点和安全库,根据官方文档,分为了正式版和预览版,思维导图如下:
正式功能:
- 虚拟线程
- 序列集合
- 弃用 Windows 32 位 x86 移植
- 准备禁止动态加载代理
- 分代 ZGC
- switch 模式匹配
- 记录模式
- 密钥封装机制 API
预览版功能:
- 字符串模板(预览)
- 外部函数和内存 API(第三次预览)
- 未命名模式和变量(预览)
- 未命名类和实例主方法(预览)
3、虚拟线程
你发任你发,我用JAVA8
提供了一种更高效、更轻量级的线程模型
1、为什么需要加入虚拟线程
在此之前,我们创建线程后需要销毁线程来释放内存,会造成大量的成本消耗,完美的解决方案就是我们用线程池,通过线程池来管理线程,并且共享线程相当于说我用的时候去租用一个线程。
这样就解决了创建和销毁的成本,但是呢,还有并发量没有解决,线程逐渐增多之后,线程之间会存在频繁切换,切换的成本很高
so 就引入了虚拟线程,使用虚拟线程进行切换,这样成本就相当于只有一个线程在工作
2、使用
代码层面兼容性很好,基本和原来的线程池没有区别
首先创建一个普通线程,实现Runnable接口
public class ThreadDemo implements Runnable{
@Override
public void run() {
System.out.println("线程名为:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
1、通过Thread直接使用
Thread thread = Thread.startVirtualThread(new ThreadDemo());
2、使用ofVirtual(),构建器模式启动虚拟线程,您可以设置线程名称、优先级、异常处理和其他配置
Thread thread = Thread.ofVirtual().name("javaxiaobear").unstarted(new ThreadDemo());
3、通过Executors调用
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
//另一种写法
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
Future<?> submit = executorService.submit(new ThreadDemo());
Object javaxiaobear = submit.get();
4、使用工厂模式创建虚拟线程,其实跟ofVirtual差不多
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(new ThreadDemo());
thread.setName("javaxiaobear");
thread.start();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
4、序列集合
官方文档:https://openjdk.org/jeps/431
Java 的集合框架缺少表示具有定义的遇到顺序的元素序列的集合类型,比如LinkedHashSet获取最后一个元素,就需要遍历整个集合,所以官方就增加了3个接口
有序集合是其
Collection
元素具有定义的遇到顺序的集合,有序集合具有第一个和最后一个元素,它们之间的元素具有后继和前驱。排序集合支持两端的通用操作,并且支持从第一个到最后一个以及从最后一个到第一个(即正向和反向)处理元素。
1、有序集合
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
2、序列集
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed(); // covariant override
}
3、序列Map
interface SequencedMap<K,V> extends Map<K,V> {
// new methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
5、弃用 Windows 32 位 x86 移植
这里就很简单,未来将不支持32位的系统了
6、准备禁止动态加载代理
当代理动态加载到正在运行的 JVM 中时发出警告。这些警告旨在帮助用户为将来的版本做好准备,该版本默认情况下不允许动态加载代理,以提高默认情况下的完整性。在启动时加载代理的可服务性工具不会导致在任何版本中发出警告.
总的来说就是在JVM中禁止动态加载代理
在 JDK 21 中,允许动态加载代理,但 JVM 在发生这种情况时会发出警告。例如:
WARNING: A {Java,JVM TI} agent has been loaded dynamically (file:/u/bob/agent.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
要允许工具动态加载代理而不发出警告,用户必须-XX:+EnableDynamicAgentLoading
在命令行上使用该选项运行。
7、分代 ZGC
ZGC(JEP 333)专为低延迟和高可扩展性而设计,通过扩展 Z 垃圾收集器 ( ZGC ) 来维护年轻对象和老对象的不同代,从而提高应用程序性能。这将使 ZGC 能够更频繁地收集年轻对象(这些对象往往会在年轻时死亡)
在 Java 21 中,我们可以这样开启分代 ZGC:
java -XX:+UseZGC -XX:+ZGenerational ...
同时,分代 ZGC 中不再需要设置年轻代大小、年轻代进入老年代所需要的 GC 次数、GC 线程数等,ZGC 将这些全部动态化,并在内部自动调优。现在只需要设置一个参数,即最大内存大小 -Xmx
。
具体详情大家可参考这篇:https://sdl.moe/post/generational-zgc/
8、switch 模式匹配
官方文档:https://openjdk.org/jeps/441
switch
通过表达式和语句的模式匹配增强 Java 编程语言。扩展模式匹配switch
允许针对多个模式测试表达式,每个模式都有一个特定的操作,以便可以简洁、安全地表达复杂的面向数据的查询,实际上就是一个语法升级,代码美观了
1、使用
public class SwitchTest {
public static void main(String[] args) {
int i = 88;
System.out.println("以前写法"+ formatter(i));
System.out.println(formatterPatternSwitch(i));
}
/**
* 以前写法
* @param obj
* @return
*/
public 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;
}
/**
* 现在写法
* @param obj
* @return
*/
public 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();
};
}
}
2、空值处理
/**
* 之前写法
* @param s
*/
public static void testFooBarOld(String s) {
if (s == null) {
System.out.println("是空值!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("javaxiaobear is Good");
}
}
/**
* 新特性写法
* @param s
*/
static void testFooBarNew(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("javaxiaobear is Good");
}
}
9、记录模式
官方文档:https://openjdk.org/jeps/440
Record Patterns的实现原理主要涉及两个方面:
- 记录类型
- 记录类型是一种新的类声明形式,通过record关键字来定义。
模式匹配
模式匹配是指根据给定的模式来匹配某个对象,并执行相应的操作。在Record Patterns中,我们可以使用instance of关键字和模式变量
来进行模式匹配。具体地说,当我们使用Record Patterns进行模式匹配时,编译器会自动为记录类型生成一个模式匹配方法。这个方法
接受一个对象作为参数兰担据给定的模式进行匹配。如果匹配成功,则将字段值绑定到相应的模式变量中,从而可以在后续代码中用。
10、密钥封装机制 API
引入密钥封装机制 (KEM) 的 API,这是一种使用公钥加密来保护对称密钥的加密技术。密钥封装是一种现代加密技术,它使用非对称或公钥加密来保护对称密钥。这样做的传统技术是使用公钥加密随机生成的对称密钥,但这需要填充并且很难证明安全。相反,密钥封装机制 (KEM) 使用公钥的属性来派生相关的对称密钥,这不需要填充。
KEM 由三个功能组成:
- 密钥对生成函数,返回包含公钥和私钥的密钥对。
- 密钥封装函数,由发送方调用,采用接收方的公钥和加密选项;它返回一个秘密密钥K 和一个密钥封装消息(在 ISO 18033-2 中称为*密文)。*发送方将密钥封装消息发送给接收方。
- 密钥解封装函数,由接收方调用,获取接收方的私钥和接收到的密钥封装消息;它返回密钥K。
import javax.crypto.KEM;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
public class KEMTest {
public static void main(String[] args) throws NoSuchAlgorithmException {
//RSA算法
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
//初始化秘钥大小
instance.initialize(1024);
//生成密钥对
KeyPair kp = instance.generateKeyPair();
//获取公钥
System.out.println(kp.getPublic());
//获取私钥
System.out.println(kp.getPrivate());
}
}