学习 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 如下新特性
找几个有代表性的着重加了学习
-Dfile.encoding=... 设置的值会返回到 java.nio.charsets.Charset.defaultCharset() 的值,它与以下命令的输出一样
但是 System.out 和 System.err 不由 Charset.defaultCharset() 控制,而是由 Console.charset() 决定
恰如 Python 的
jwebserver 只支持 HEAD 和 GET, 和 HTTP/1.1 协议。
Java 18 还提供更多使用 API 编程方式使用 WebServe.
com.sun.net.httpserver.HttpServer 在 JDK 1.6 开始就有了,搭配 HttpHandler 可实现对不同 path 如
等,它们让用 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 新加的
试着实现一个简单的 RESTFull API
启动后测试
用 @snippet 标签可支持内联或外部文件中的代码片断,@snippet 支持属性,内联的代码语法高亮默认为 Java, 外部代码的语法高亮由文件扩展名推断。
注意,用 @snippet 标识的内联代码中也不能用 /* ...*/, 并且 HTML 标签需转义,外部文件不受此约束。@snippet 内联代码中可以有大括号 { 或 },但碰到第一个不平衡(无匹配的
比如下面的
用 javadoc Test.java 生成的 API 文档中显示为
首先用了 @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
这样该行中的
@replace
@link
指定语法
和更多的 @snippet, highlight, replace, link 属性请见 https://openjdk.org/jeps/413#Snippet-tag-reference。
它目前还处于孵化期,预告一下,到了 Java 25, Vector API 还在第十次孵化,有点难产,即使出来了也有些尴尬,方便不如 NumPy, 性能比不及 C++。
使用起来还真不方便
为第三方实现主机名和地址解析提供了 SPI(Service Provider Interface) 扩展。默认是操作系统本地 OS 的主机名和地址解析实现,在某些情况下可能需要用到第三方的实现,比如在 AWS 的 Fargate 实例中可以通过
它所提供的 API 是 java.net.spi.InetAddressResolverProvider,比如自己的一个实现类是 com.example.MyInetAddressResolver, 就必须在 classpath 下加上文件 /META-INF/services/java.net.spi.InetAddressResolverProvider, 并写上一行内容为 com.example.myInetAddressResolver.
从目前 Java 18 预览版的 switch 模式匹配功能还相当的弱,没有条件判断,不能拆解 record 中的字段值等。
随着 GC 算法的不断改进,我们应该更信任 GC 的能力,所以不要碰 Object.finalize() 方法, 以后想碰也碰不到了。资源的释放强烈建议用 try-with-reources 语句。
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
除了从 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 如下新特性
- 400: UTF-8 by Default
- 408: Simple Web Server
- 413: Code Snippets in Java API Documentation
- 416: Reimplement Core Reflection with Method Handles
- 417: Vector API (Third Incubator)
- 418: Internet-Address Resolution SPI
- 419: Foreign Function & Memory API (Second Incubator)
- 420: Pattern Matching for switch (Second Preview)
- 421: Deprecate Finalization for Removal
找几个有代表性的着重加了学习
标准 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通过 -b 和 -p 可指定绑定的网络设备与端口号。
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/
jwebserver 只支持 HEAD 和 GET, 和 HTTP/1.1 协议。
Java 18 还提供更多使用 API 编程方式使用 WebServe.
com.sun.net.httpserver.HttpServer 在 JDK 1.6 开始就有了,搭配 HttpHandler 可实现对不同 path 如
/, /hello 进行相应的响应。而 Java 18 新添了- com.sun.net.httpserver.SimpleFileServer
- 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
1import java.io.IOException;
2import java.net.InetSocketAddress;
3import java.util.concurrent.Executors;
4
5public class TestWebServer {
6
7 public static void main(String[] args) throws IOException {
8 HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
9
10 HttpHandler rootHandler = HttpHandlers.handleOrElse(request -> request.getRequestMethod().equals("GET"),
11 HttpHandlers.of(200, Headers.of("Content-Type", "application/json"), "{\"status\": \"ok\"}"),
12 HttpHandlers.of(405, Headers.of(), "Method Not Allowed"));
13
14 HttpHandler userHandler = HttpHandlers.handleOrElse(request -> request.getRequestMethod().equals("POST"),
15 exchange -> {
16 String requestBody = new String(exchange.getRequestBody().readAllBytes());
17 exchange.getResponseHeaders().set("Content-Type", "application/json");
18 byte[] responseBytes = ("{\"message\": \"User created\", \"data\": " + requestBody + "}").getBytes();
19 exchange.sendResponseHeaders(201, responseBytes.length);
20 exchange.getResponseBody().write(responseBytes);
21 exchange.getResponseBody().close();
22 }, HttpHandlers.of(405, Headers.of(), "Method Not Allowed"));
23
24 server.createContext("/", rootHandler);
25 server.createContext("/users", userHandler);
26
27 server.setExecutor(Executors.newFixedThreadPool(10));
28
29 server.start();
30 System.out.println("Server started on port 8080");
31 }
32}启动后测试
$ 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 内联代码中可以有大括号 { 或 },但碰到第一个不平衡(无匹配的
{) 的 } 为代码结束点。比如下面的
1/**
2 * <pre>{@code
3 * int sum = widgets.stream()
4 * .filter(w -> w.getColor() == RED)
5 * .mapToInt(w -> w.getWeight())
6 * .sum();
7 * }</pre>
8 */用 javadoc Test.java 生成的 API 文档中显示为
首先用了 @Snippet : 引入的代码在 IntelliJ IDEA 中就是语法高亮显示了
如在生成的 Java API 文档中,只是多加了一个灰色边框,右上角有个复制按钮,没有实质性的语法加亮。这与 Java 18 的 JEP 413 描述不相符。
真正的语法高亮还要后续版本的努力,而且即使到了 Java 25, 用了 javadoc --add-stylesheet prism.min.css --add-script prism.min.js 也不见 Java 语法高亮,这又能作一个专题,暂且搁在一边。其他一些 @snippet 相关的功能。
引用外部文件
1/**
2 * {@snippet file="ShowOptional.java" region="example"}
3 */1// @start region="example"
2...
3// @end围绕的区块的代码。
外部文件也可以用类名引用, 如 {@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++。
1final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
2float[] a = {2, 3, 5, 7};
3float[] b = {1.5f, 2.0f, 3, 3};
4var va = FloatVector.fromArray(SPECIES, a, 0);
5var vb = FloatVector.fromArray(SPECIES, b, 0);
6var vc = va.add(vb);
7System.out.println(vc); // [3.0, 6.0, 15.0, 21,0]使用起来还真不方便
互联网地址解析 SPI
为第三方实现主机名和地址解析提供了 SPI(Service Provider Interface) 扩展。默认是操作系统本地 OS 的主机名和地址解析实现,在某些情况下可能需要用到第三方的实现,比如在 AWS 的 Fargate 实例中可以通过
得到 IP 地址,就可以实现自己的实现,或者用自己的 JNI 实现。curl $ECS_CONTAINER_METADATA_URI_V4/task | jq '.Containers[0].Networks[0].IPv4Addresses[0]'
它所提供的 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 可以有返回值,下面看个例子1switch (o) {
2 case String s -> System.out.println("It's a string: " + s);
3 case Integer i -> System.out.println("It's an integer: " + i);
4 case null -> System.out.println("It's null");
5 default -> System.out.println("Unknown type: " + o.getClass());
6}从目前 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, 看看还有什么值得一提的要点- ZGC, SerialGC, ParallelGC 纷纷支持字符串去重,可多节约一些内存。除了关注 ZGC, 另两种垃圾回收方式也不会在产品环境中使用到
- Charset.forName(String charsetName, Charset fallback) 新方法支持 fallback 为默认值
- javax.annotation.processing.Messager 类新加了 printError, printWarning, printNode 方法
- G1 Heap Region 大小从原来的最大 32M 直增致 512M。默认的 region 大小仍然是 32M, 用 -XX:G1HeapRegionSize 改变 Heap Region 大小
- Flight Recorder 事件支持 finalize(), 不是 finalize() 行将被删除吗?看来要多用飞行记录器,而非原来的 JMX 了
- javadoc 命令支持 --add-script 命令为 JavaDoc 文档附加 Javascript 脚本文件,对 @snippet 代码的语法加亮就要靠它了
- 移除了 JDK 1.4 的 DatagramSocketImpl 实现
- Thread.stop() 方法将要被删除
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。