概述
JDK 18 于 2022 年 3 月 22 日正式发布。
JEP(Java Enhancement Proposal)Java增强提案
CSR(Compatibility & Specification Review) 兼容性和规范审查
变动说明
官网:
更多参考:
docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…
重要变更和信息
JDK 18 包含 9 个 新特性 ,分别为:
- JEP 400: UTF-8 by Default 默认UTF-8编码
- JEP 408: Simple Web Server 简易Web服务器
- JEP 413: Code Snippets in Java API Documentation 支持在 Java API 文档中加入代码片段
- JEP 416: Reimplement Core Reflection with Method Handles 用方法句柄重新实现核心反射
- JEP 417: Vector API (Third Incubator) 向量 API(第三次孵化)
- JEP 418: Internet-Address Resolution SPI 互联网地址解析 SPI
- JEP 419: Foreign Function & Memory API (Second Incubator) 外部函数和内存 API(第三次孵化)
- JEP 420: Pattern Matching for switch (Second Preview) Switch 的模式匹配(第二次预览)
- JEP 421: Deprecate Finalization for Removal 弃用 Finalization 功能
而其中与开发过程中直接相关的特性主要包括:JEP 420(Switch 表达式的模式匹配)等。
下载地址
你可以从这个链接下载Oracle JDK版本,更多版本下载。
也可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。
Java18新特性总结
1、JEP 420: Switch 的模式匹配(第二次预览)
JEP 420 (specification/language)
这仍然是一个预览特性
功能进化
switch表达式的功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 5 | 首次引入,仅支持整型数据类型(如 byte, short, char, 和 int)及枚举类型 | ||
| Java 7 | 支持 String 类型 | ||
| Java 12 | 预览特性 | JEP 325 | 支持Switch表达式(箭头函数) |
| Java 13 | 预览特性 | JEP 354 | 加入 yield 语句来替代 break 语句,用于从 switch 表达式返回值 |
| Java 14 | 正式特性 | JEP 361 | 前2个版本的新特性转为正式特性 |
| Java 17 | 预览特性 | JEP 406 | 引入模式匹配的Switch表达式作为预览特性 |
| Java 18 | 第二次预览 | JEP 420 | 调整优化 |
在 Java 16 中, JEP 394 将 instanceof的模式匹配发布为正式属性。虽然可以不需要强制转换了,但是仍然需要大量的 if...else。而 Switch 表达式虽然简化了if...else,但是它无法像instanceof一样不需要强制转换。为了解决这个痛点,Java 17引入模式匹配的Switch表达式特性 ,目前该特性为预览特性。
该特性扩展了 switch 表达式和语句,允许它们使用模式匹配,这就意味着我们可以在 switch 的 case 标签中使用模式,如类型模式,使得代码更加灵活和表达性更强。而且也无需进行显式的类型转换了。例如,可以使用 case Integer i 这样的语法来匹配并自动转换类型。
但是,不知道小伙伴们注意没有,Switch 表达式只有一种类型,比如:我有一个诗人类(Poet),它有3个实现类:唐朝诗人(TangPoet)、宋朝诗人(SongPoet)、汉朝诗人(HanPoet),我要根据诗人的类型进行不同处理 :
Poet poet = ... // 诗人
switch (poet.getClass().getName()) {
case "my.poet.TangPoet":
TangPoet tp = (TangPoet) obj;
// 处理唐朝诗人
break;
case "my.poet.SongPoet":
SongPoet sp = (SongPoet) obj;
// 处理宋朝诗人
break;
case "my.poet.HanPoet":
HanPoet hp = (HanPoet) obj;
// 处理汉朝诗人
break;
// 其他类型
}
这个强转显然比较麻烦。所以,参考Java 17中,参考instanceof的模式匹配,为switch表达式引入了模式匹配功能作为预览特性。
Switch 模式匹配
在 Java 17 中,switch 表达式允许使用模式匹配来处理对象类型,这样就可以直接在 switch 语句中检查和转换类型,而不需要额外的 if...else 结构和显式类型转换。
case后面可以跟的标签主要有:
- 类型标签
- null标签
- 守卫标签
- enum或常量值
类型标签
允许在 switch 语句的 case 分支中直接匹配对象的类型。例如,case String s 允许你在该分支中直接作为字符串类型的 s 来使用,避免了显式的类型检查和强制类型转换。
举个例子:
@Test
public void switchTest() {
// 不是用switch根据类型判断
Object[] objects = { "Hello", "Java", "17", 666, 0.618 };
for (Object obj : objects) {
if (obj instanceof Integer v) {
System.out.printf("为整型 :%s %n", v);
} else if (obj instanceof Float v) {
System.out.printf("为Float:%s %n", v);
} else if (obj instanceof Double v) {
System.out.printf("为Double:%s %n", v);
} else if (obj instanceof String v) {
System.out.printf("为字符串:%s %n", v);
} else {
System.out.printf("其他类型:%s %n", obj);
}
}
}
我们用 Switch 表达式来改造下:
@Test
public void switchTest() {
Object[] objects = { "Hello", 123, "World", "Java", 3.14, "skjava" };
for (Object obj: objects) {
switch (obj) {
case Integer v -> System.out.println("为整数型:" + v);
case Float v -> System.out.println("为浮点型:" + v);
case Double v -> System.out.println("为双精度浮点数:" + v);
case String v -> System.out.println("为字符串:" + v);
default -> System.out.println("其他类型:" + obj);
}
}
}
相比上面的 if...else 简洁了很多。同时在 Java 17之前,Switch选择器表达式只支持特定类型,即基本整型数据类型byte、short、char和int;对应的装箱形式Byte、Short、Character和Integer;String类;枚举类型。现在有了类型模式,Switch 表达式可以是任何类型啦。
null标签
当switch允许任何引用类型的选择器表达式,那么我们需要留意null的情况,在Java17之前,向switch语句传递一个null值,会抛出一个NullPointerException,现在可以通过类型模式,将 null 检查作为一个单独的case标签来处理,如下:
@Test
public void switchTest() {
Object[] objects = { "Hello", "Java", "17", 142857, 0.618 };
for (Object obj: objects) {
switch (obj) {
case Integer v -> System.out.println("为整数型:" + v);
case Float v -> System.out.println("为浮点型:" + v);
case Double v -> System.out.println("为双精度浮点数:" + v);
case String v -> System.out.println("为字符串:" + v);
case null -> System.out.println("为空值");
default -> System.out.println("其他类型:" + obj);
}
}
}
case null 可以直接匹配值为 null 的情况。
守卫标签
与匹配常量值的case标签不同,模式case标签可以对应多个变量值。这通常会导致switch规则右侧出现条件语句。
根据字符串长度判断诗句是五言绝句还是七言绝句,代码如下:
@Test
public void switchCaseCaseTest() {
String[] poems = { "千山鸟飞绝", "春城无处不飞花", "红豆生南国", "二月春风似剪刀","念奴娇" };
for (String poem : poems) {
switch (poem) {
case null -> System.out.println("为空值");
case String s -> {
if (s.length() == 5)
System.out.printf("五言绝句:%s%n", s);
else if (s.length() == 7)
System.out.printf("七言绝句:%s%n", s);
else
System.out.printf("不知道是啥:%s%n", s);
}
}
}
}
这里的问题是,使用单一模式(即类型)来区分case就只能判断一种情况。我们只能在模式匹配中再通过if……else判断来区分不同的情况,来对一个模式的细化。这时,我们可以是使用switch中的when子句指定模式case标签的条件,例如,case String s when if (s.length() == 5)。表示当类型为String并且字符串长度为5的时候,我们将这种case标签称为守卫case标签,将布尔表达式称为保护。
@Test
public void switchCaseCaseTest() {
String[] poems = { "千山鸟飞绝", "春城无处不飞花", "红豆生南国", "二月春风似剪刀","念奴娇" };
for (String poem : poems) {
switch (poem) {
case null -> System.out.println("为空值");
case String s when s.length() == 5 -> System.out.printf("五言绝句:%s%n", s);
case String s when s.length() == 7 -> System.out.printf("七言绝句:%s%n", s);
case String s -> System.out.printf("不知道是啥:%s%n", s); //剩余情况,仍然走这个
}
}
}
使用守卫标签,我们可以编写更灵活和表达性强的代码。
2、JEP 400:默认UTF-8编码
在Java 18之前,Java的标准字符集(Charset)是根据操作系统的区域设置决定的。这意味着在不同的操作系统和区域设置下,Java程序的行为可能会不同,特别是在处理文本数据时。这种不一致性导致了许多问题,尤其是在跨平台部署和国际化应用程序时。而且 UTF-8 支持几乎所有语言的字符。所以 Java 18 将UTF-8设为Java平台的默认字符集,解决了跨平台的一致性问题。从此以后,Java 在处理文本数据时的行为在各种平台上就更加一致,减少了与字符编码相关的错误和混淆。
3、JEP 408:简易Web服务器
Java 18 引入该特性的主要目的为开发者提供一个轻量级、简单易用的 HTTP 服务器,用于原型制作、测试和开发环境。
Java 18 提供了一个命令:jwebserver。利用这个命令,我们可以启动一个简单的 、最小化的静态 Web 服务器,但是它不支持 CGI 和 Servlet,所以最好的使用场景是用来测试、教育以及演示等需求。
关于jwebserver需要注意:
- 构建目的是应用于测试与教学,不是为了替代
Jetty、Nginx等高级服务器 - 不提供身份验证、访问控制或加密等安全功能
- 仅支持
HTTP/1.1,不支持HTTPS - 仅支持
GET、HEAD请求 - 可以通过命令行、Java类启动
jwebserver的参数
示例1
准备页面
编写一个简单的页面
<html>
<head>
<meta charset="utf-8">
</head>
</body>
<h1>天地玄黄,宇宙洪荒</h1>
</body>
</html>
启动
在页面所在的目录,我这里是mytest中,使用命令行启动。
打开终端,输入jwebserver命令:我这里路径没有设置到Path里,所以使用全路径访问。如果设置了Path,直接使用入jwebserver即可。
访问控制台提示的路径http://127.0.0.1:8000/,就可以看到内容了。
访问时可以看到终端输出日志
127.0.0.1 - - [27/2月/2024:18:53:19 +0800] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [27/2月/2024:18:53:20 +0800] "GET /favicon.ico HTTP/1.1" 404 -
使用java启动
public class JWebServerTest {
public static void main(String[] args) {
System.out.println("服务器启动");
// System.out.println(Paths.get("").toAbsolutePath());
var addr = new InetSocketAddress(8001);
// 路径需要使用绝对路径
var server = SimpleFileServer.createFileServer(addr, Path.of(Paths.get("").toAbsolutePath().toString()),
SimpleFileServer.OutputLevel.INFO);
server.start();
}
}
如果使用jwebserver来实现,实际上就是下面的命令:路径根据直接实际情况调整
jwebserver -p 8001 -d F:\workspacenew2023-04\mylab\mytest[目录的绝对路径] -o info
4、JEP 413:支持在 Java API 文档中加入代码片段
JEP 413 (tools/javadoc(tool))
在 Javadoc 工具中引入了 @snippet 标签,允许文档作者在 API 文档中嵌入源代码片段。
在之前的 Java 文档中,代码示例通常是文本形式,容易过时且难以验证正确性。通过这个特性,我们可以确保示例代码的准确性和时效性。
而且,我们还可以设置高亮(按字符串、按正则)、替换代码片段、源代码引用、链接到 API 文档的其他部分、甚至是自定义的 CSS 样式,使文档更加生动和易于阅读。
在Java 18之前,已经有一个@code标签,可以用于在JavaDoc中编写小段的代码内容,@snipppet对其进行了增强。具体可以参考JEP。
5、JEP 416:用方法句柄重新实现核心反射
JEP 416 (core-libs/java.lang:reflect)
JEP416用方法句柄重新实现了核心反射。使用方法句柄代替 Java 语言的反射机制中的原生方法,降低java.lang.reflect和java.lang.invoke API的维护和开发成本。部分功能可能受影响。可能出现的问题包括:
- 检查内部生成的反射类(如
MagicAccessorImpl的子类)的代码不再工作,必须更新。 - 试图破坏封装并将
Method、field和Constructor类的私有最终修饰符字段的值更改为与基础成员不同的代码可能会导致运行时错误。必须更新此类代码。
为了降低这种兼容性风险,可以使用-Djdk.reflect.useDirectMethodHandle=false来启用旧的实现作为解决方法。我们将在未来的版本中删除旧的核心反射实现。-Djdk.reflect.useDirectMethodHandle=false的变通方法将在此时停止工作。
降低了对未来语言功能的反射支持的维护和开发成本,并且简化了Hotspot VM代码。
6、JEP 417:向量 API(第三次孵化)
JEP 417 (core-libs)
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 16 | 第一次孵化 | JEP 338 | 提供一个平台无关的方式来表达向量计算,能够充分利用现代处理器上的向量硬件指令。 |
| Java 17 | 第二次孵化 | JEP 414 | 改进 |
| Java 18 | 第三次孵化 | JEP 417 | 进一步增强 |
向量 API 是在 Java 16 中作为孵化特性引入的,Java 17 经历第二次孵化,Java 18 再一次孵化。
引入API来表示向量计算,这些向量计算在运行时可靠地编译为支持的CPU架构上的最佳向量指令,从而实现优于等效标量计算的性能。Java 18 对向量 API 进行了进一步的改进和增强,以更好地利用硬件功能,提高 Java 在数值计算和机器学习等领域的性能。
7、JEP 418:互联网地址解析 SPI
JEP 418 (core-libs/java.net)
引入一个用于主机名称和地址解析的服务提供者接口(SPI),从而实现java.net.InetAddress可以使用平台内置解析器以外的解析器。这种新的SPI允许替换操作系统的本地解析程序,该解析程序通常配置为使用本地主机文件和域名系统(DNS)的组合。
8、JEP 419:外部函数和内存 API(第二次孵化)
JEP 419 (core-libs)
孵化阶段。
功能进化
| java版本 | 特性类型 | JEP | 特性 |
|---|---|---|---|
| Java 14 | 孵化特性 | JEP 370 | 引入了外部内存访问 API作为孵化特性 |
| Java 15 | 第二次孵化 | JEP 383 | 优化外部内存访问 API |
| Java 16 | 孵化特性 | JEP 389 | 引入了外部链接器 API |
| Java 16 | 第三次孵化 | JEP 393 | 功能优化 |
| Java 17 | 孵化特性 | JEP 412 | 引入了外部函数和内存 API |
| Java 18 | 第二次孵化 | JEP 419 | 改进 |
外部函数和内存 API 是在 Java 17 中作为孵化器引入的,它提供对本机代码的静态类型的纯Java访问,其主要目的是改善 Java 与本地代码(如 C 或 C++)的互操作性。此API与Foreign-Memory API(JEP 393)一起,将大大简化绑定到本机库的错误处理过程。
一般Java想要调用本地代码需要使用Java Native Interface (JNI),但是JNI操作比较复杂而且性能有限。
外部函数和内存API提供了一套更简洁的API,用于调用本地函数和处理本地内存,降低了复杂性,而且还设计了更多的安全保护措施,降低了内存泄露和应用崩溃的风险。
主要是通过两个组件实现的:
- Foreign Function Interface (FFI) : 允许 Java 代码直接调用非 Java 代码,比如 C/C++ 代码。
- Foreign Memory Access API:提供了一种安全的方法来访问不受
JVM管理的内存。
9、JEP 421:弃用 Finalization 功能
Finalization是 Java 早期版本引入的功能,用于在对象被垃圾收集器销毁之前执行清理操作。然而,这个机制存在诸多问题,比如性能不佳、行为不可预测、容易导致内存泄漏等。
Java 18 将该功能标记为废弃,在将来的版本中移除。 finalize() 将不建议再被使用。目前默认情况下,Finalization仍处于启用状态,但可以禁用以便于早期测试。在将来的版本中,默认情况下它将被禁用,在以后的版本中它将被删除。使用了Finalization功能的库和应用程序的维护人员应该考虑迁移到其他资源管理技术,如try-with-resources statement 和 cleaners。
10、移除的APIs、工具、容器
参考: