有了 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 如下新特性
- 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
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 新添了
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.Executors; public class TestWebServer { public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); HttpHandler rootHandler = HttpHandlers.handleOrElse(request -> request.getRequestMethod().equals("GET"), HttpHandlers.of(200, Headers.of("Content-Type", "application/json"), "{\"status\": \"ok\"}"), HttpHandlers.of(405, Headers.of(), "Method Not Allowed")); HttpHandler userHandler = HttpHandlers.handleOrElse(request -> request.getRequestMethod().equals("POST"), exchange -> { String requestBody = new String(exchange.getRequestBody().readAllBytes()); exchange.getResponseHeaders().set("Content-Type", "application/json"); byte[] responseBytes = ("{\"message\": \"User created\", \"data\": " + requestBody + "}").getBytes(); exchange.sendResponseHeaders(201, responseBytes.length); exchange.getResponseBody().write(responseBytes); exchange.getResponseBody().close(); }, HttpHandlers.of(405, Headers.of(), "Method Not Allowed")); server.createContext("/", rootHandler); server.createContext("/users", userHandler); server.setExecutor(Executors.newFixedThreadPool(10)); server.start(); System.out.println("Server started on port 8080"); } } |
启动后测试
$ 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 3 4 5 6 7 8 |
/** * <pre>{@code * int sum = widgets.stream() * .filter(w -> w.getColor() == RED) * .mapToInt(w -> w.getWeight()) * .sum(); * }</pre> */ |
用 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 3 |
/** * {@snippet file="ShowOptional.java" region="example"} */ |
引入 ShowOptional.java 的代码,可使用 region 指定在 ShowOptional.java 中的只以如下方式
1 2 3 |
// @start region="example" ... // @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++。
1 2 3 4 5 6 7 |
final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED; float[] a = {2, 3, 5, 7}; float[] b = {1.5f, 2.0f, 3, 3}; var va = FloatVector.fromArray(SPECIES, a, 0); var vb = FloatVector.fromArray(SPECIES, b, 0); var vc = va.add(vb); System.out.println(vc); // [3.0, 6.0, 15.0, 21,0] |
使用起来还真不方便
互联网地址解析 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 可以有返回值,下面看个例子
1 2 3 4 5 6 |
switch (o) { case String s -> System.out.println("It's a string: " + s); case Integer i -> System.out.println("It's an integer: " + i); case null -> System.out.println("It's null"); default -> System.out.println("Unknown type: " + o.getClass()); } |
从目前 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() 方法将要被删除
本文链接 https://yanbin.blog/java-18-new-features/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。