Java11、Java17、Java21
1.Java11
Java 11 于 2018 年 9 月 25 日正式发布,这是很重要的一个版本!Java 11 和 2017 年 9 月份发布的 Java 9 以及 2018 年 3 月份发布的 Java 10 相比,其最大的区别就是:在长期支持(Long-Term-Support)方面,Oracle 表示会对 Java 11 提供大力支持,这一支持将会持续至 2026 年 9 月。这是据 Java 8 以后支持的首个长期版本。(1)HTTP Client 标准化
Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 Http Client API 进行了标准化,在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。并且,Java 11 中,Http Client 的包名由 jdk.incubator.http
改为java.net.http
,该 API 通过 CompleteableFuture
提供非阻塞请求和响应语义。
var request = HttpRequest.newBuilder() .uri(URI.create("https://javastack.cn")) .GET() .build(); var client = HttpClient.newHttpClient(); // 同步 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); // 异步 client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println);
(2)String 增强
//判断字符串是否为空 " ".isBlank();//true //去除字符串首尾空格 " Java ".strip();// "Java" //去除字符串首部空格 " Java ".stripLeading(); // "Java " //去除字符串尾部空格 " Java ".stripTrailing(); // " Java" //重复字符串多少次 "Java".repeat(3); // "JavaJavaJava" //返回由行终止符分隔的字符串集合。 "A\nB\nC".lines().count(); // 3 "A\nB\nC".lines().collect(Collectors.toList());
(3)Optional 增强
新增了isEmpty()
方法来判断指定的 Optional
对象是否为空。
var op = Optional.empty(); System.out.println(op.isEmpty());//判断指定的 Optional 对象是否为空
(4)ZGC(可伸缩低延迟垃圾收集器)
ZGC 即 Z Garbage Collector,是一个可伸缩的、低延迟的垃圾收集器。ZGC 主要为了满足如下目标进行设计:- GC 停顿时间不超过 10ms
- 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
- 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
- 方便在此基础上引入新的 GC 特性和利用 colored 针以及 Load barriers 优化奠定基础
- 当前只支持 Linux/x64 位平台
ZGC(转正)
Java11 的时候 ,ZGC 还在试验阶段。
经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java 15 已经可以正式使用了!
不过,默认的垃圾回收器依然是 G1。你可以通过下面的参数启动 ZGC:
java -XX:+UseZGC className
2.Java17
Java 17 在 2021 年 9 月 14 日正式发布,是一个长期支持(LTS)版本。Spring 6.x 和 Spring Boot 3.x 最低支持的就是 Java 17。这次更新共带来 14 个新特性:Random
、ThreadLocalRandom
和SplittableRandom
来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。 PRNG 用来生成接近于绝对随机数序列的数字序列。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。
只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。 RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom"); // 使用时间戳作为随机数种子 RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis()); // 生成随机数 randomGenerator.nextInt(10);
(2)弃用 Applet API 以进行删除
Applet API 用于编写在 Web 浏览器端运行的 Java 小程序,很多年前就已经被淘汰了,已经没有理由使用了。
Applet API 在 Java 9 时被标记弃用(JEP 289),但不是为了删除。
(3)switch 的类型匹配(预览)
正如instanceof
一样, switch
也紧跟着增加了类型匹配自动转换功能。对于 null
值的判断也进行了优化(不用if,用 case null
)。
instanceof // Old code if (o instanceof String) { String s = (String)o; ... use s ... } // New code if (o instanceof String s) { ... use s ... } // 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(); }; }
(4)删除实验性的 AOT 和 JIT 编译器
在 Java 9 的 JEP 295,引入了实验性的提前 (AOT) 编译器,在启动虚拟机之前将 Java 类编译为本机代码。
Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。
(5)弃用安全管理器以进行删除
弃用安全管理器以便在将来的版本中删除。安全管理器可追溯到 Java 1.0,多年来,它一直不是保护客户端 Java 代码的主要方法,也很少用于保护服务器端代码。为了推动 Java 向前发展,Java 17 弃用安全管理器,以便与旧版 Applet API ( JEP 398)一起移除。
(6)外部函数和内存 API(孵化)
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 JEP 412提出。第二轮孵化由JEP 419 提出并集成到了 Java 18 中,预览由 JEP 424 提出并集成到了 Java 19 中。
(7)向量 API(第二次孵化)
该孵化器 API 提供了一个 API 的初始迭代以表达一些向量计算,这些计算在运行时可靠地编译为支持的 CPU 架构上的最佳向量硬件指令,从而获得优于同等标量计算的性能,充分利用单指令多数据(SIMD)技术(大多数现代 CPU 上都可以使用的一种指令)。尽管 HotSpot 支持自动向量化,但是可转换的标量操作集有限且易受代码更改的影响。该 API 将使开发人员能够轻松地用 Java 编写可移植的高性能向量算法。
3.Java21
JDK 21 于 2023 年 9 月 19 日 发布,这是一个非常重要的版本,里程碑式。
JDK21 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17 和 JDK21 这四个长期支持版了。
JDK 21 共有 15 个新特性,这篇文章会挑选其中较为重要的一些新特性进行详细介绍:
(1)字符串模板(预览)
${}
,我们可以将变量的值直接嵌入到字符串中,而不需要手动处理。在运行时,Java 编译器会将这些占位符替换为实际的变量值。并且,表达式支持局部变量、静态/非静态字段甚至方法、计算结果等特性。//old code message = "Greetings " + name + "!"; message = String.format("Greetings %s!", name); //concatenation message = new MessageFormat("Greetings {0}!").format(name); message = new StringBuilder().append("Greetings ").append(name).append("!").toString(); //new code String message = STR."Greetings \{name}!";
- STR 是模板处理器。
\{name}
为表达式,运行时,这些表达式将被相应的变量值替换。
Java 目前支持三种模板处理器:
- STR:自动执行字符串插值,即将模板中的每个嵌入式表达式替换为其值(转换为字符串)。
- FMT:和 STR 类似,但是它还可以接受格式说明符,这些格式说明符出现在嵌入式表达式的左边,用来控制输出的样式
- RAW:不会像 STR 和 FMT 模板处理器那样自动处理字符串模板,而是返回一个
StringTemplate
对象,这个对象包含了模板中的文本和表达式的信息
StringTemplate.Processor
接口来创建自己的模板处理器。message = STR."Greetings \{name}!"; message = STR."Greetings \{getName()}!"; message = STR."Greetings \{this.name}!";
int x = 10, y = 20; String s = STR."\{x} + \{y} = \{x + y}"; //"10 + 20 = 30"
(2)序列化集合
JDK 21 引入了一种新的集合类型:Sequenced Collections(序列化集合,也叫有序集合),这是一种具有确定出现顺序(encounter order)的集合(无论我们遍历这样的集合多少次,元素的出现顺序始终是固定的)。序列化集合提供了处理集合的第一个和最后一个元素以及反向视图(与原始集合相反的顺序)的简单方法。
Sequenced Collections 包括以下三个接口:
SequencedCollection
、
SequencedSet
、
SequencedMap
SequencedCollection
接口继承了 Collection
接口, 提供了在集合两端访问、添加或删除元素以及获取集合的反向视图的方法。
interface SequencedCollection<E> extends Collection<E> { // New Method SequencedCollection<E> reversed(); // Promoted methods from Deque<E> void addFirst(E); void addLast(E); E getFirst(); E getLast(); E removeFirst(); E removeLast(); }
List
和 Deque
接口实现了SequencedCollection
接口。以 ArrayList
为例,演示一下实际使用效果:
ArrayList<Integer> arrayList = new ArrayList<>(); arrayList.add(1); // List contains: [1] arrayList.addFirst(0); // List contains: [0, 1] arrayList.addLast(2); // List contains: [0, 1, 2] Integer firstElement = arrayList.getFirst(); // 0 Integer lastElement = arrayList.getLast(); // 2 List<Integer> reversed = arrayList.reversed(); System.out.println(reversed); // Prints [2, 1, 0]
SequencedSet
接口直接继承了 SequencedCollection
接口并重写了 reversed()
方法。SortedSet
和 LinkedHashSet
实现了SequencedSet
接口。SequencedMap
接口继承了 Map
接口, 提供了在集合两端访问、添加或删除键值对、获取包含 key 的 SequencedSet
、包含 value 的 SequencedCollection
、包含 entry(键值对) 的 SequencedSet
以及获取集合的反向视图的方法。SortedMap
和LinkedHashMap
实现了SequencedMap
接口。(3)分代 ZGC
// 启用分代ZGC java -XX:+UseZGC -XX:+ZGenerational ...
在未来版本中,官方会把 ZGenerational 设为默认值,即默认打开 ZGC 的分代 GC。在更晚的版本中,非分代 ZGC 就被移除。分代 ZGC 可以显著减少垃圾回收过程中的停顿时间,并提高应用程序的响应性能。这对于大型 Java 应用程序和高并发场景下的性能优化非常有价值。
(4)switch 的模式匹配
增强 Java 中的 switch 表达式和语句,允许在 case 标签中使用模式。当模式匹配时,执行 case 标签对应的代码。
在下面的代码中,switch 表达式使用了类型模式来进行匹配。
(5)未命名类和实例 main 方法 (预览)
这个特性主要简化了 main
方法的的声明。
//Old public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } //New class HelloWorld { void main() { System.out.println("Hello, World!"); } } //未命名的类允许我们不定义类名 void main() { System.out.println("Hello, World!"); }
(6)虚拟线程
虚拟线程在 Java 19 中进行了第一次预览,由JEP 425提出。JDK 20 中是第二次预览。最终,虚拟线程在 JDK21 顺利转正。
虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),由 JVM 调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。
在引入虚拟线程之前,java.lang.Thread
包已经支持所谓的平台线程,也就是没有虚拟线程之前,我们一直使用的线程。JVM 调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。
关于平台线程和系统内核线程的对应关系多提一点:在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个平台线程对应一个系统内核线程。Solaris 系统是一个特例,HotSpot VM 在 Solaris 上支持多对多和一对一。
相比较于平台线程来说,虚拟线程是廉价且轻量级的,使用完后立即被销毁,因此它们不需要被重用或池化,每个任务可以有自己专属的虚拟线程来运行。虚拟线程暂停和恢复来实现线程之间的切换,避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂,可以有效减少编写、维护和观察高吞吐量并发应用程序的工作量。
虚拟线程在其他多线程语言中已经被证实是十分有用的,比如 Go 中的 Goroutine、Erlang 中的进程。
4 种创建虚拟线程的方式可以看出,官方为了降低虚拟线程的门槛,尽力复用原有Thread
线程类,这样可以平滑过渡到虚拟线程的使用。Thread thread = Thread.ofVirtual(fn).start(); 1.使用 Thread.startVirtualThread() 创建 2.使用 Thread.ofVirtual() 创建 3.使用 ThreadFactory 创建 4.使用 Executors.newVirtualThreadPerTaskExecutor()创建
(7)外部函数和内存 API(第三次预览)
(8)未命名模式和变量(预览)
未命名模式和变量使得我们可以使用下划线 _
表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。
未命名变量的典型场景是 try-with-resources
语句、 catch
子句中的异常变量和for
循环。当变量不需要使用的时候就可以使用下划线 _
代替,这样清晰标识未被使用的变量。