学习 Java 18 的新特性

有了 AI 是不是就用不着了解语言特性本身呢?用 Vibe Coding 难道就无所不能呢?如果是的话那些找工作的也就无需刷 LeetCode 了。试想 Vibe Coding 产生了成堆的代码,即使创建了 Pull Request, 也不是给人 Review 的,也只能由 AI 来 Review, 到头来就是 AI 与 AI 自己玩,有 Bug 也只有 AI 看得懂。以后的屎山代码是一车一车的来。

除了从 JDK 官方每个版本的 What's New in JDK 18 - New Features and Enhancements, 还可以看 OpenJDK JDK 18 列出的更简明的新特性。自 JDK 10 之后,每一版的新特性由链接 https://openjdk.org/projects/jdk/<version>/ 查看,如 JDK 10 新特性链接为 https://openjdk.org/projects/jdk/10/

https://openjdk.org/projects/jdk/18/ 列出了 JDK 18 如下新特性

找几个有代表性的着重加了学习

标准 Java API 默认采用 UTF-8 字符集

这应该是大势所趋,这避免了不同平台 FileWriter, FileReader 由于不同字符集时产生的乱码。原来有时候要设置 -Dfile.encoding=...(运行时用 System.setProperty() 方法设置该属性无效),到 Java 18 不用这么做了,因为标准 API 中在没有传递字符集参数时默认为 UTF-8

-Dfile.encoding=... 设置的值会返回到 java.nio.charsets.Charset.defaultCharset() 的值,它与以下命令的输出一样

java -XshowSettings:properties -version 2>&1 | grep file.encoding

默认应用 UTF-8 字符集的标准 Java APIs 包括 java.io 包中的  InputStream, FileReader, OutputStreamWriter, FileWriter, PrintStream; java.util 包中的 Formatter, Scanner; 以及 java.net 包中 URLEncoder, URLDecoder。

但是 System.out 和 System.err 不由 Charset.defaultCharset() 控制,而是由  Console.charset() 决定

简单 Web 服务

Java 也开始像其他众多语言(如 node.js, .net core, Python(SimpleHTTPServer 或 http.server))一样提供了简单的 Web 服务 API, 而不再是启动 Java 的 Web 服务总是依赖于 Tomcat, Undertow, Jetty 等。当然这个简单的 Web 服务也是只为测试提供方便,不能作为商业 Web 服务使用。

恰如 Python 的 python -m http.server 会启动 http(8000) 服务,默认能浏览目录内容一样,在 Java 18 中新增的 jwebserver 命令有同等的功效,同样的默认 8000 端口号,同样浏览能浏览目录

jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /Users/yanbin and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

通过 -b 和 -p 可指定绑定的网络设备与端口号。

jwebserver 只支持 HEAD 和 GET, 和 HTTP/1.1 协议。

Java 18 还提供更多使用 API 编程方式使用 WebServe.

com.sun.net.httpserver.HttpServer 在 JDK 1.6 开始就有了,搭配 HttpHandler 可实现对不同 path 如 /, /hello 进行相应的响应。而 Java 18 新添了

  1. com.sun.net.httpserver.SimpleFileServer
  2. com.sun.net.httpserver.HttpHandlers

等,它们让用 JDK 内置的 jdk.httpserver 模块下的包 com.sun.net.httpserver 和 sun.net.httpserver 编写 WebServer 变得更快捷,诸如使用到 HttpServer, HttpsServer,HttpHandler, HttpExchange, Filter 等概念,能实现一个完备的 HTTP 服务.

而我们看到的 jwebserver 命令就是对应的 sun.net.httpserver.simpleserver.JWebServer。

除了直用 jwebserver 命令外,也能像 Python 运行模块那样,用 Java 9 新加的 -m 参数来起来 jwebserver, 如

java -m jdk.httpserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /Users/yanbin and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

试着实现一个简单的 RESTFull API

启动后测试

$ curl http://localhost:8080
{"status": "ok"}%
$ curl -X POST http://localhost:8080/users --data 'hello'
{"message": "User created", "data": hello}%

Java Doc 中插入代码片断

Java 18 觉得原来的 Java Doc 中使用 <pre>{@code ...}</pre> 还不过瘾, 因为它功能比较简陋,不支持语法高亮,其中还不能用 /*...*/ 注释,不能包含 HTML 标签,而新引入的 @snippet 标签可以解决前面的所有问题。

用 @snippet 标签可支持内联或外部文件中的代码片断,@snippet 支持属性,内联的代码语法高亮默认为 Java, 外部代码的语法高亮由文件扩展名推断。

注意,用 @snippet 标识的内联代码中也不能用 /* ...*/, 并且 HTML 标签需转义,外部文件不受此约束。@snippet 内联代码中可以有大括号 { 或 },但碰到第一个不平衡(无匹配的 {) 的 } 为代码结束点。

比如下面的

用 javadoc Test.java 生成的 API 文档中显示为

如果换成 @snippet 效果如何呢

首先用了 @Snippet : 引入的代码在 IntelliJ IDEA 中就是语法高亮显示了

如在生成的 Java API 文档中,只是多加了一个灰色边框,右上角有个复制按钮,没有实质性的语法加亮。这与 Java 18 的 JEP 413 描述不相符。

真正的语法高亮还要后续版本的努力,而且即使到了 Java 25, 用了 javadoc --add-stylesheet prism.min.css --add-script prism.min.js 也不见 Java 语法高亮,这又能作一个专题,暂且搁在一边。

其他一些 @snippet 相关的功能。

引用外部文件

引入 ShowOptional.java 的代码,可使用 region 指定在 ShowOptional.java 中的只以如下方式

围绕的区块的代码。

外部文件也可以用类名引用, 如 {@snippet class="..."}

@highlight

System.out.println("Hello World!"); // @highlight substring="println"

这样该行中的 println 会被加粗显示

for (var arg : args) { // @highlight region regex = "\barg\b"

正式表达式的写法

@replace

System.out.println("Hello World!"); // @replace regex='".*"' replacement="..."

在 JavaDoc 中把双引号中的字符替换为 ..., 如 System.out.println("...")

@link

System.out.println("Hello World!"); // @link substring="System.out" target="System#out"

产生链接

指定语法

{@snippet lang=properties: 

和更多的 @snippet, highlight, replace, link 属性请见 https://openjdk.org/jeps/413#Snippet-tag-reference

使用方法句柄重新实现反射的核心

对 API 的使用不影响,只是内部对性能进行了优化,不多说

向量 API (第三次孵化)

向量运算现在基本上是 NumPy, Scipy 的天下,核心部件是 NumPy 的 ndarray。而 Java 目前要表达了一个向量(Vector) 的实现成本很高,更没有相关的计算方法。再往上级别的矩阵(Matrix),张量(Tensor), 所以 Java 的机器学习训练方面几乎没有立锥之地。

它目前还处于孵化期,预告一下,到了 Java 25, Vector API 还在第十次孵化,有点难产,即使出来了也有些尴尬,方便不如 NumPy, 性能比不及 C++。

使用起来还真不方便

互联网地址解析 SPI

为第三方实现主机名和地址解析提供了 SPI(Service Provider Interface) 扩展。默认是操作系统本地 OS 的主机名和地址解析实现,在某些情况下可能需要用到第三方的实现,比如在 AWS 的 Fargate 实例中可以通过

curl $ECS_CONTAINER_METADATA_URI_V4/task | jq '.Containers[0].Networks[0].IPv4Addresses[0]'

得到 IP 地址,就可以实现自己的实现,或者用自己的 JNI 实现。

它所提供的 API 是 java.net.spi.InetAddressResolverProvider,比如自己的一个实现类是 com.example.MyInetAddressResolver, 就必须在 classpath 下加上文件 /META-INF/services/java.net.spi.InetAddressResolverProvider, 并写上一行内容为 com.example.myInetAddressResolver.

访问外部函数与内存的 API(第二次孵化)

要实现从 JVM 中安全的访问 JVM 外的函数和内存,那不是就是要用来替代 JNI 的吗?这一特性在 Java 22 中已转正,届时再体验。

Switch 的模式匹配(第二次预览)

该特性在 Java 21 中转正。简单介绍一下,由原来的 swtich (s) { case ...: } 变成为 switch(s) { case ...->, 到了 Java 21 的正式版中进一步得到加强,持附条件判断并且成为一个表达式,即 switch 可以有返回值,下面看个例子

从目前 Java 18 预览版的 switch 模式匹配功能还相当的弱,没有条件判断,不能拆解 record 中的字段值等。

Object.finalize() 方法标记为待删除

这个方法其实不应该再重提,许久以前就不推荐覆盖该方法,现在 Java 教程大概也不提及它。它的设计初衷是让对象最后还有一次机会被 GC 执行该方法释放某些系统资源。但该方法的存在太不确定,比如不知道何时会被调用(如今也更没人会去主动调用 System.gc() 方法了吧),finanlize() 太容易被滥用,做了不该做的事,反而可能招致最后关头待回收的对象重新被复活等。

随着 GC 算法的不断改进,我们应该更信任 GC 的能力,所以不要碰 Object.finalize() 方法, 以后想碰也碰不到了。资源的释放强烈建议用 try-with-reources 语句。

其他相关的改进

再浏览一下 https://www.oracle.com/java/technologies/javase/18all-relnotes.html#NewFeature, 看看还有什么值得一提的要点

  1. ZGC, SerialGC, ParallelGC 纷纷支持字符串去重,可多节约一些内存。除了关注 ZGC, 另两种垃圾回收方式也不会在产品环境中使用到
  2. Charset.forName(String charsetName,  Charset fallback) 新方法支持 fallback 为默认值
  3. javax.annotation.processing.Messager 类新加了 printError, printWarning, printNode 方法
  4. G1 Heap Region 大小从原来的最大 32M 直增致 512M。默认的 region 大小仍然是 32M, 用 -XX:G1HeapRegionSize 改变 Heap Region 大小
  5. Flight Recorder 事件支持 finalize(), 不是 finalize() 行将被删除吗?看来要多用飞行记录器,而非原来的 JMX 了
  6. javadoc 命令支持 --add-script 命令为 JavaDoc 文档附加 Javascript 脚本文件,对 @snippet 代码的语法加亮就要靠它了
  7. 移除了 JDK 1.4 的 DatagramSocketImpl 实现
  8. Thread.stop() 方法将要被删除

本文链接 https://yanbin.blog/java-18-new-features/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

guest

0 Comments
Inline Feedbacks
View all comments