Java
Java 18 在2022 年 3 月 22 日正式发布,Java 18 不是一个长期支持版本,这次更新共带来 9 个新功能。
OpenJDK Java 18 下载:https://jdk.java.net/18/
OpenJDK Java 18 文档:https://openjdk.java.net/projects/jdk/18/
Java 18新特性 - 图1
值得注意的是:JDK 17 是一个长期支持 (LTS) 版本,将获得 Oracle 至少八年的支持,但 JDK 18 将是一个短期功能版本,只支持六个月。可以在 java.net(https://jdk.java.net/18/) 上找到适用于 Linux、Windows 和 MacOS 的 JDK 18 的尝鲜版本。
JDK 18 新功能一览:

JEP 描述
JEP 400 默认为 UTF-8
JEP 408 简单的网络服务器
JEP 413 Java API 文档中的代码片段
JEP 416 使用方法句柄重新实现核心反射
JEP 417 Vector API(三次孵化)
JEP 418 互联网地址解析 SPI
JEP 419 Foreign Function & Memory API (二次孵化)
JEP 420 switch 模式匹配(二次预览)
JEP 421 弃用完成删除

JEP 400:指定 UTF-8 作为标准 Java API 的默认字符集。

通过此更改,依赖于默认字符集的 API 将在所有实现、操作系统、区域设置和配置中保持一致。
JDK 一直都是支持 UTF-8 字符编码,这次是把 UTF-8 设置为了默认编码,也就是在不加任何指定的情况下,默认所有需要用到编码的 JDK API 都使用 UTF-8 编码,这样就可以避免因为不同系统,不同地区,不同环境之间产生的编码问题。
Mac OS 默认使用 UTF-8 作为默认编码,但是其他操作系统上,编码可能取决于系统的配置或者所在区域的设置。如中国大陆的 windows 使用 GBK 作为默认编码。很多同学初学 Java 时可能都遇到过一个正常编写 Java 类,在 windows 系统的命令控制台中运行却出现乱码的情况。
使用下面的命令可以输出 JDK 的当前编码。

  1. # Mac 系统,默认 UTF-8
  2. ~ java -XshowSettings:properties -version 2>&1 | grep file.encoding
  3. file.encoding = UTF-8
  4. file.encoding.pkg = sun.io
  5. ~

下面编写一个简单的 Java 程序,输出默认字符编码,然后输出中文汉字 “你好”,看看 Java 18 和 Java 17 运行区别。
系统环境:Windows 11

  1. import java.nio.charset.Charset;
  2. public class Hello{
  3. public static void main(String[] args) {
  4. System.out.println(Charset.defaultCharset());
  5. System.out.println("你好");
  6. }
  7. }

从下面的运行结果中可以看到,使用 JDK 17 运行输出的默认字符编码是 GBK,输出的中文 “你好” 已经乱码了;乱码是因为 VsCode 默认的文本编辑器编码是 UTF-8,而中国地区的 Windows 11 默认字符编码是 GBK,也是 JDK 17 默认获取到的编码,所以会在控制台输出时乱码;而使用 JDK 18 输出的默认编码就是 UTF-8,所以可以正常的输出中文 “你好”
Java 18新特性 - 图2

JEP 408:引入一个简单的 Web 服务器。

在 Java 18 中,提供了一个新命令 jwebserver,运行这个命令可以启动一个简单的 、最小化的静态Web 服务器,它不支持 CGI 和 Servlet,所以最好的使用场景是用来测试、教育、演示等需求。
其实在如 Python、Ruby、PHP、Erlang 等许多平台都提供了开箱即用的 Web 服务器,可见一个简单的Web 服务器是一个常见的需求,Java 一直没有这方面的支持,现在可以了。
在 Java 18 中,使用 jwebserver 启动一个 Web 服务器,默认发布的是当前目录。
在当前目录创建一个网页文件 index.html

  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  4. </head>
  5. <body>
  6. <h1>标题</h1>
  7. </body>
  8. </html>

启动 jwebserver.

  1. bin ./jwebserver
  2. Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
  3. Serving /Users/darcy/develop/jdk-18.jdk/Contents/Home/bin and subdirectories on 127.0.0.1 port 8000
  4. URL http://127.0.0.1:8000/

浏览器访问:
浏览器访问测试
有请求时会在控制台输出请求信息:

  1. 127.0.0.1 - - [26/3月/2022:16:53:30 +0800] "GET /favicon.ico HTTP/1.1" 404 -
  2. 127.0.0.1 - - [26/3月/2022:16:55:13 +0800] "GET / HTTP/1.1" 200 -

通过 help 参数可以查看 jwebserver 支持的参数。

  1. bin ./jwebserver --help
  2. Usage: jwebserver [-b bind address] [-p port] [-d directory]
  3. [-o none|info|verbose] [-h to show options]
  4. [-version to show version information]
  5. Options:
  6. -b, --bind-address - 绑定地址. Default: 127.0.0.1 (loopback).
  7. For all interfaces use "-b 0.0.0.0" or "-b ::".
  8. -d, --directory - 指定目录. Default: current directory.
  9. -o, --output - Output format. none|info|verbose. Default: info.
  10. -p, --port - 绑定端口. Default: 8000.
  11. -h, -?, --help - Prints this help message and exits.
  12. -version, --version - Prints version information and exits.
  13. To stop the server, press Ctrl + C.

JEP 413:支持在 Java API 文档中加入代码片段。

为 JavaDoc 的 Standard Doclet 引入一个 @snippet 标记,以简化 API 文档中嵌入示例源代码的难度。
以前在Java代码的注释中如果要写一些样例非常麻烦,甚至还要进行字符转义。现在Java注释引入了一个新的标记 @snippet 来解决注释中包含代码片段样例的问题。
在 Java 18 之前,使用 <pre>{@code ...}</pre> 来引入代码片段。

  1. /**
  2. * 时间工具类
  3. * Java 18 之前引入代码片段:
  4. * <pre>{@code
  5. * public static String timeStamp() {
  6. * long time = System.currentTimeMillis();
  7. * return String.valueOf(time / 1000);
  8. * }
  9. * }</pre>
  10. *
  11. */

生成 Javadoc 之后,效果如下:
Javadoc 代码片段

高亮代码片段

从 Java 18 开始,可以使用 @snippet 来生成注释,且可以高亮某个代码片段。

  1. /**
  2. * 在 Java 18 之后可以使用新的方式
  3. * 下面的代码演示如何使用 {@code Optional.isPresent}:
  4. * {@snippet :
  5. * if (v.isPresent()) {
  6. * System.out.println("v: " + v.get());
  7. * }
  8. * }
  9. *
  10. * 高亮显示 println
  11. *
  12. * {@snippet :
  13. * class HelloWorld {
  14. * public static void main(String... args) {
  15. * System.out.println("Hello World!"); // @highlight substring="println"
  16. * }
  17. * }
  18. * }
  19. *
  20. */

效果如下,更直观,效果更好。
Java 18 Javadoc

正则高亮代码片段

甚至可以使用正则来高亮某一段中的某些关键词:

  1. /**
  2. * 正则高亮:
  3. * {@snippet :
  4. * public static void main(String... args) {
  5. * for (var arg : args) { // @highlight region regex = "\barg\b"
  6. * if (!arg.isBlank()) {
  7. * System.out.println(arg);
  8. * }
  9. * } // @end
  10. * }
  11. * }
  12. */

生成的 Javadoc 效果如下:
Java 18新特性 - 图6

替换代码片段

可以使用正则表达式来替换某一段代码。

  1. /**
  2. * 正则替换:
  3. * {@snippet :
  4. * class HelloWorld {
  5. * public static void main(String... args) {
  6. * System.out.println("Hello World!"); // @replace regex='".*"' replacement="..."
  7. * }
  8. * }
  9. * }
  10. */

这段注释会生成如下 Javadoc 效果。

  1. class HelloWorld {
  2. public static void main(String... args) {
  3. System.out.println(...);
  4. }
  5. }

内联使用

  1. /**
  2. * The following code shows how to use {@code Optional.isPresent}:
  3. * {@snippet :
  4. * if (v.isPresent()) {
  5. * System.out.println("v: " + v.get());
  6. * }
  7. * }
  8. */

引用外部片段

  1. /**
  2. * The following code shows how to use {@code Optional.isPresent}:
  3. * {@snippet file="ShowOptional.java" region="example"}
  4. */

ShowOptional.java就是引用的源代码:

  1. public class ShowOptional {
  2. void show(Optional<String> v) {
  3. // @start region="example"
  4. if (v.isPresent()) {
  5. System.out.println("v: " + v.get());
  6. }
  7. // @end
  8. }
  9. }

附:Javadoc 生成方式

  1. # 使用 javadoc 命令生成 Javadoc 文档
  2. bin ./javadoc -public -sourcepath ./src -subpackages com -encoding utf-8 -charset utf-8 -d ./javadocout
  3. # 使用 Java 18 的 jwebserver 把生成的 Javadoc 发布测试
  4. bin ./jwebserver -d /Users/darcy/develop/javadocout

访问测试:
Java 18新特性 - 图7

JEP 416 :用方法句柄重新实现核心反射。

在 java.lang.invoke 的方法句柄之上,重构 java.lang.reflect 的方法、构造函数和字段,使用方法句柄处理反射的底层机制将减少 java.lang.reflect 和 java.lang.invoke 两者的 API 维护和开发成本。
Java 18 改进了 java.lang.reflect.Method、Constructor 的实现逻辑,使之性能更好,速度更快。这项改动不会改动相关 API ,这意味着开发中不需要改动反射相关代码,就可以体验到性能更好反射。
OpenJDK 官方给出了新老实现的反射性能基准测试结果。
Java 18 之前:

  1. Benchmark Mode Cnt Score Error Units
  2. ReflectionSpeedBenchmark.constructorConst avgt 10 68.049 ± 0.872 ns/op
  3. ReflectionSpeedBenchmark.constructorPoly avgt 10 94.132 ± 1.805 ns/op
  4. ReflectionSpeedBenchmark.constructorVar avgt 10 64.543 ± 0.799 ns/op
  5. ReflectionSpeedBenchmark.instanceFieldConst avgt 10 35.361 ± 0.492 ns/op
  6. ReflectionSpeedBenchmark.instanceFieldPoly avgt 10 67.089 ± 3.288 ns/op
  7. ReflectionSpeedBenchmark.instanceFieldVar avgt 10 35.745 ± 0.554 ns/op
  8. ReflectionSpeedBenchmark.instanceMethodConst avgt 10 77.925 ± 2.026 ns/op
  9. ReflectionSpeedBenchmark.instanceMethodPoly avgt 10 96.094 ± 2.269 ns/op
  10. ReflectionSpeedBenchmark.instanceMethodVar avgt 10 80.002 ± 4.267 ns/op
  11. ReflectionSpeedBenchmark.staticFieldConst avgt 10 33.442 ± 2.659 ns/op
  12. ReflectionSpeedBenchmark.staticFieldPoly avgt 10 51.918 ± 1.522 ns/op
  13. ReflectionSpeedBenchmark.staticFieldVar avgt 10 33.967 ± 0.451 ns/op
  14. ReflectionSpeedBenchmark.staticMethodConst avgt 10 75.380 ± 1.660 ns/op
  15. ReflectionSpeedBenchmark.staticMethodPoly avgt 10 93.553 ± 1.037 ns/op
  16. ReflectionSpeedBenchmark.staticMethodVar avgt 10 76.728 ± 1.614 ns/op

Java 18 的新实现:

  1. Benchmark Mode Cnt Score Error Units
  2. ReflectionSpeedBenchmark.constructorConst avgt 10 32.392 ± 0.473 ns/op
  3. ReflectionSpeedBenchmark.constructorPoly avgt 10 113.947 ± 1.205 ns/op
  4. ReflectionSpeedBenchmark.constructorVar avgt 10 76.885 ± 1.128 ns/op
  5. ReflectionSpeedBenchmark.instanceFieldConst avgt 10 18.569 ± 0.161 ns/op
  6. ReflectionSpeedBenchmark.instanceFieldPoly avgt 10 98.671 ± 2.015 ns/op
  7. ReflectionSpeedBenchmark.instanceFieldVar avgt 10 54.193 ± 3.510 ns/op
  8. ReflectionSpeedBenchmark.instanceMethodConst avgt 10 33.421 ± 0.406 ns/op
  9. ReflectionSpeedBenchmark.instanceMethodPoly avgt 10 109.129 ± 1.959 ns/op
  10. ReflectionSpeedBenchmark.instanceMethodVar avgt 10 90.420 ± 2.187 ns/op
  11. ReflectionSpeedBenchmark.staticFieldConst avgt 10 19.080 ± 0.179 ns/op
  12. ReflectionSpeedBenchmark.staticFieldPoly avgt 10 92.130 ± 2.729 ns/op
  13. ReflectionSpeedBenchmark.staticFieldVar avgt 10 53.899 ± 1.051 ns/op
  14. ReflectionSpeedBenchmark.staticMethodConst avgt 10 35.907 ± 0.456 ns/op
  15. ReflectionSpeedBenchmark.staticMethodPoly avgt 10 102.895 ± 1.604 ns/op
  16. ReflectionSpeedBenchmark.staticMethodVar avgt 10 82.123 ± 0.629 ns/op

可以看到在某些场景下性能稍微好些。

JEP 417:Vector API(第三孵化器)。

引入一个 API 来表达向量计算,这些计算在运行时可以编译为支持的 CPU 架构上的最佳向量指令,从而实现优于等效标量计算的性能。
在 Java 16 中引入一个新的 API 来进行向量计算,它可以在运行时可靠的编译为支持的 CPU 架构,从而实现更优的计算能力。
在 Java 17 中改进了 Vector API 性能,增强了例如对字符的操作、字节向量与布尔数组之间的相互转换等功能。
现在在 JDK 18 中将继续优化其性能。

JEP 418:互联网地址解析 SPI。

定义用于主机名和地址解析的服务提供者接口 (SPI),以便java.net.InetAddress可以使用平台内置解析器以外的解析器。
对于互联网地址解析 SPI,为主机地址和域名地址解析定义一个 SPI,以便java.net.InetAddress可以使用平台内置解析器以外的解析器。

  1. InetAddress inetAddress = InetAddress.getByName("www.yuque.com");
  2. System.out.println(inetAddress.getHostAddress());
  3. // 输出
  4. // 106.14.229.49

JEP 419:外部函数和内存 API(第二孵化器)。

引入了一个新 API,Java 程序可以通过它与 Java 运行时之外的代码和数据进行互操作。通过有效地调用外部函数(即 JVM 外的代码),并安全地访问外部内存(即不由 JVM 管理的内存),外部函数和内存 API 使 Java 程序能够调用本机库并处理本机数据,而不具有 JNI 的脆弱性和危险。
Foreign Function & Memory API ( JEP 419 ) 是此版本中实现的更重要的 JEP 之一,因为它是Project Panama中包含的孵化组件之一。 Panama 正在简化将 Java 程序连接到非 Java 组件的过程。这一特殊功能在其第二次孵化迭代中引入了一个 APIJava 程序通过该 API 调用Native类库并处理Native数据。目的是取代设计的非常不理想的Java Native Interface (JNI)
大家都知道其它语言有非常棒的一些类库,但是Java想调用其它语言的类库目前需要使用JNI。但是JNI被设计得太复杂了,让很多Java开发者难以上手。如果这一状况得到改变,那么利用Java去调用一些C或者C++音视频处理库和Python的机器学习库将是非常容易的事情。
新的 API 允许 Java 开发者与 JVM 之外的代码和数据进行交互,通过调用外部函数,可以在不使用 JNI 的情况下调用本地库。
这是一个孵化功能;需要添加--add-modules jdk.incubator.foreign来编译和运行 Java 代码,Java 18 改进了相关 API ,使之更加简单易用。
历史

  • • Java 14 JEP 370 (opens new window)引入了外部内存访问 API(孵化器)。
  • • Java 15 JEP 383 (opens new window)引入了外部内存访问 API(第二孵化器)。
  • • Java 16 JEP 389 (opens new window)引入了外部链接器 API(孵化器)。
  • • Java 16 JEP 393 (opens new window)引入了外部内存访问 API(第三孵化器)。
  • • Java 17 JEP 412 (opens new window)引入了外部函数和内存 API(孵化器)。

    JEP 420:switch 模式匹配表达式。

    使用 switch 表达式和语句的模式匹配以及对模式语言的扩展来增强 Java 编程语言。将模式匹配扩展到 switch 允许针对多个模式测试表达式,每个模式都有特定的操作,可以简洁安全地表达复杂的面向数据的查询。
    实现的唯一真正影响 Java 语言的 JEP 是Pattern Matching for switch ( JEP 420 ),它在 Java 17 中首次预览(这是第二次预览)。其目的是“通过对switch 表达式和语句的模式匹配以及对模式语言的扩展来增强 Java 编程语言 。在 Java 16 中,JEP 394扩展了instanceof运算符以采用类型模式并执行模式匹配: ```java // Old code if (o instanceof String) { String s = (String)o; … use s … }

// New code if (o instanceof String s) { … use s … }

  1. 使用`instanceof`后无需再对对象进行类型转换就可以使用其真实的类型。<br />**Java 14**又引入了`switch`表达式:
  2. ```java
  3. int numLetters = switch (day) {
  4. case MONDAY, FRIDAY, SUNDAY -> 6;
  5. case TUESDAY -> 7;
  6. case THURSDAY, SATURDAY -> 8;
  7. case WEDNESDAY -> 9;
  8. default -> 11;
  9. };

如果这两个能结合起来,switch能进行模式匹配的话,下面的句子将大大简化:

  1. static String formatter(Object o) {
  2. String formatted = "unknown";
  3. if (o instanceof Integer i) {
  4. formatted = String.format("int %d", i);
  5. } else if (o instanceof Long l) {
  6. formatted = String.format("long %d", l);
  7. } else if (o instanceof Double d) {
  8. formatted = String.format("double %f", d);
  9. } else if (o instanceof String s) {
  10. formatted = String.format("String %s", s);
  11. }
  12. return formatted;
  13. }

JEP 420的预览特性,将会把上面冗长的代码简化为:

  1. static String formatterPatternSwitch(Object o) {
  2. return switch (o) {
  3. case Integer i -> String.format("int %d", i);
  4. case Long l -> String.format("long %d", l);
  5. case Double d -> String.format("double %f", d);
  6. case String s -> String.format("String %s", s);
  7. default -> o.toString();
  8. };
  9. }

是不是更加清晰了
switch 可以和 null 进行结合判断:

  1. static void testFooBar(String s) {
  2. switch (s) {
  3. case null -> System.out.println("Oops");
  4. case "Foo", "Bar" -> System.out.println("Great");
  5. default -> System.out.println("Ok");
  6. }
  7. }

case 时可以加入复杂表达式:

  1. static void testTriangle(Shape s) {
  2. switch (s) {
  3. case Triangle t && (t.calculateArea() > 100) ->
  4. System.out.println("Large triangle");
  5. default ->
  6. System.out.println("A shape, possibly a small triangle");
  7. }
  8. }

case 时可以进行类型判断:

  1. sealed interface S permits A, B, C {}
  2. final class A implements S {}
  3. final class B implements S {}
  4. record C(int i) implements S {} // Implicitly final
  5. static int testSealedExhaustive(S s) {
  6. return switch (s) {
  7. case A a -> 1;
  8. case B b -> 2;
  9. case C c -> 3;
  10. };
  11. }

JEP 421:弃用 Finalization 功能。

Object对象有一个finalize方法,该方法用于实例被垃圾回收器回收的时触发的操作。当 GC (垃圾回收器) 确定不存在对该对象的有更多引用时,对象的垃圾回收器就会调用这个方法。当时它的设计用来避免内存泄露,现在已经有了更好的替代方案try-with-resources和Java 9引入的 java.lang.ref.Cleaner
因此,所有该方法会被标记为过时,未来将被移除。
Java 1.0 中引入的 Finalization 旨在帮助避免资源泄漏问题,然而这个功能存在延迟不可预测、行为不受约束,以及线程无法指定等缺陷,导致其安全性、性能、可靠性和可维护性方面都存在问题,因此将其弃用,用户可选择迁移到其他资源管理技术,例如try-with-resources 语句和清洁器。
2022 年 1 月 20 日会进入 Rampdown 第二阶段,初始和最终候选(RC)版本将分别于明年 2 月 10 日和 2 月 24 日发布,稳定版 JDK 18 将在 2022 年 3 月 22 日发布,可在 JDK 公告页中查看最新消息。
JDK 公告页:https://openjdk.java.net/projects/jdk/18/

参考资料

Java 18: http://openjdk.java.net/projects/jdk/18/
java.net.InetAddress: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/net/InetAddress.html
JEP 419: https://openjdk.java.net/jeps/419
Project Panama: https://openjdk.java.net/projects/panama/
JEP 420: https://openjdk.java.net/jeps/420
JEP 394: https://openjdk.java.net/jeps/394
默认为 UTF-8: https://openjdk.java.net/jeps/400
简单的网络服务器: https://openjdk.java.net/jeps/408
Java API 文档中的代码片段: https://openjdk.java.net/jeps/413
使用方法句柄重新实现核心反射: https://openjdk.java.net/jeps/416
Vector API(三次孵化): https://openjdk.java.net/jeps/417
互联网地址解析 SPI: https://openjdk.java.net/jeps/418
oreign Function & Memory API (二次孵化): https://openjdk.java.net/jeps/419
switch 模式匹配(二次预览): https://openjdk.java.net/jeps/420
弃用完成删除: https://openjdk.java.net/jeps/421
JEP 370 (opens new window): https://openjdk.java.net/jeps/370
JEP 383 (opens new window): https://openjdk.java.net/jeps/383
JEP 389 (opens new window): https://openjdk.java.net/jeps/389
JEP 393 (opens new window): https://openjdk.java.net/jeps/393
JEP 412 (opens new window): https://openjdk.java.net/jeps/412