📑 Java 演进系列总览 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25

Java 11 于 2018-09-25 正式发布,是继 Java 8 之后的第一个长期支持(LTS)版本。它承接了 Java 9 的模块系统与 Java 10 的 var 局部变量推断——这些在中间非 LTS 版本定稿的特性在此 LTS 中可用,并为后续版本确立了半年一次的发布节奏。Java 11 的主线变化在平台与工具链:标准化 HTTP 客户端、单文件源码启动、JFR 开源,以及从 JDK 中移除 Java EE 与 CORBA 模块,使 Java 由"带 Java EE 的 JDK"收敛为更精简的现代平台基线。

HTTP 客户端

标准库内置的 java.net.http.HttpClient(JEP 321)取代了老旧的 HttpURLConnection——后者 API 设计于 2000 年前后,不支持异步、不可变性差、流式处理繁琐。新客户端原生支持 HTTP/2(通过 HTTP/1.1 协商升级)、WebSocket、同步与异步两种调用方式,且 HttpClientHttpRequest 均为不可变对象,可安全共享。

同步调用 send 阻塞直至响应完成,适合简单脚本与后台任务;异步调用 sendAsync 返回 CompletableFuture,可直接与 Java 8 的组合式异步 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
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/"))
.timeout(Duration.ofSeconds(5))
.GET()
.build();

// 同步:阻塞至响应完成
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());

// 异步:返回 CompletableFuture,不阻塞当前线程
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();

超时语义分两层:connectTimeout 限制建立连接的时间,请求级的 timeout 限制整个请求的完成时间,二者不可混淆。响应体通过 BodyHandlers 声明式处理——ofStringofFileofInputStream 分别对应文本、落盘与流式消费。

Lambda 参数中的 var

Java 10 引入 var 局部变量推断(在此 LTS 中可用),Java 11 将其扩展到 lambda 参数(JEP 323)。表面上看 (var a, var b) -> a + b(a, b) -> a + b 等价,前者的实际价值只有一个:允许为 lambda 参数附加注解。Java 语法规定参数注解必须依托显式类型,推断场景下 var 就是那个显式类型。

规则是"全有或全无":要么所有参数都声明 var,要么全部省略,混用即编译错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@FunctionalInterface
interface Joiner {
String join(String a, String b);
}

// 合法:全部使用 var
Joiner j1 = (var a, var b) -> a + b;

// 合法:全部省略 var
Joiner j2 = (a, b) -> a + b;

// 编译错误:var 与省略形式混用
// Joiner j3 = (var a, b) -> a + b;

// var 的实际意义:为参数附加注解
Joiner j4 =
(@Deprecated var a, var b) -> a + b;

日常代码中若无需注解,直接省略 var 即可;该特性主要服务于框架与校验工具在 lambda 参数上表达约束的场景。

字符串与文件 API

StringFiles 补齐了一批高频小工具(JEP 331 与 JDK 11 API 更新),消除了此前依赖 Apache Commons 或手写循环的样板代码。字符串方面重点关注 Unicode 语义:strip 按 Unicode 空白定义裁剪,trim 只处理 ASCII 码位不大于 20 的字符,处理多语言文本时应优先 strip

1
2
3
4
5
6
7
8
9
// String 新方法
String s = " hello ";
System.out.println(s.isBlank()); // false
System.out.println(" ".isBlank()); // true
System.out.println(s.strip()); // "hello"
System.out.println(s.trim()); // "hello"(仅 ASCII 空白)
System.out.println("-".repeat(20));
"java\npython\ngo".lines()
.forEach(System.out::println);

文件方面,Files.readStringFiles.writeString 把"读一个小文本文件"从 BufferedReader + try-with-resources 样板压缩为一行。两者均按 UTF-8 处理(可用带 Charset 的重载覆盖),适合配置、模板等小文件;大文件仍应使用流式 API。

1
2
3
4
5
6
7
8
9
10
11
import java.nio.file.Files;
import java.nio.file.Path;

Path path = Path.of("config.txt");

// 一次性写入(默认 UTF-8)
Files.writeString(path, "name=java\nversion=11\n");

// 一次性读取
String content = Files.readString(path);
System.out.println(content);

配套的还有 Path.of(取代 Paths.get,消除两套等价 API)与 Files.isSameFile 等增量改进,均属同一批易用性更新。

单文件源码启动

JEP 330 允许直接以源文件为入口启动程序:java Hello.java 在内存中编译后立即执行 main,不产生 .class 文件。这把 Java 的"写第一个程序"成本拉到与脚本语言同级——教学、工具原型、一次性维护脚本都不再需要 javac 与 classpath 布局。

1
2
3
4
5
6
7
8
$ cat Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java 11");
}
}
$ java Hello.java
Hello, Java 11

两个边界要清楚:其一,该模式按文件名而非类名定位源码,类名与文件名不必一致,但传入的必须是 .java 文件,否则 java Hello 仍被解释为运行已编译的 Hello 类;其二,它只做单文件编译,依赖第三方库时仍需构建工具,生产代码不因此放弃 javac/Maven/Gradle 流程。

安全性与可观测性

JDK Flight Recorder(JFR,JEP 328)在 Java 11 中开源并入 JDK。它以接近零开销的方式持续采集 JVM 与应用的运行事件(GC、锁竞争、方法采样、分配压力),配合 JDK Mission Control 分析,是生产环境性能诊断的首选工具——取代了此前需要商业授权的 Oracle JMC 生态。

1
2
3
4
5
6
7
8
9
# 启动时即开启 60 秒连续记录
java -XX:StartFlightRecording=duration=60s,\
filename=app.jfr MyApp

# 运行中的进程通过 jcmd 触发记录
jcmd <pid> JFR.start duration=60s filename=app.jfr

# 用 jfr 命令行工具查看事件
jfr print --events jdk.GCPhasePause app.jfr

传输层方面,Java 11 提供 TLS 1.3 支持(JEP 319 的后继实现):握手往返更少、默认前向保密,SSLSocket 与新 HTTP 客户端均可协商使用。同一批运行时更新中,Epsilon 垃圾回收器(JEP 318,无操作 GC,用于性能基准与超短生命周期任务)与 ZGC(JEP 333,实验性低延迟 GC,暂停目标亚毫秒)也一并亮相——注意这两者是 HotSpot/JDK 的实现选择而非 Java SE 规范保证,其他 JDK 实现未必提供,且 ZGC 当时处于实验阶段,需 -XX:+UnlockExperimentalVMOptions 显式开启。

常用特性总结

  • HTTP 客户端java.net.http.HttpClient 支持 HTTP/2 与 WebSocket,send 同步、sendAsync 异步返回 CompletableFuture
  • var 扩展:lambda 参数可声明 var,全有或全无;为 lambda 参数附加注解的唯一途径。
  • 字符串方法isBlank 判空白,strip 按 Unicode 裁剪,repeat 重复,lines 按行流式处理。
  • 文件读写Files.readString/Files.writeString 一次性 UTF-8 文本读写,Path.of 取代 Paths.get
  • 单文件启动java Hello.java 直接运行源文件,适合脚本与原型。
  • JFR 开源:零开销生产级 profiling,-XX:StartFlightRecording 开箱即用。
  • TLS 1.3:更快的握手与前向保密。
  • 运行时选项(HotSpot/JDK):Epsilon 与实验性 ZGC 属实现选择,非 Java SE 保证。

迁移与兼容性

从 Java 8 升级到 Java 11 最大的断裂点是 Java EE 模块移除:java.xml.bind(JAXB)、java.xml.ws(JAX-WS)、java.activationjava.corbajava.transaction(仅 EE 部分)不再随 JDK 发行,需在构建中显式加入对应依赖(如 jakarta.xml.bind-api 及实现)。JavaFX 同样从 Oracle JDK 11 中剥离,改由 OpenJFX 单独发布。

其余迁移成本集中在工具链:Nashorn JavaScript 引擎在 11 中标记废弃;-source/-target 最低支持提升至 Java 7;此前版本默认的 --illegal-access 对内部 API 的宽容在 11 中仅剩一次警告机会,依赖 sun.misc.Unsafe 等内部 API 的库必须升级。实践中建议先在 CI 中以 --add-opens 临时放行定位问题,再推动依赖库升级消除警告;应用层 API(集合、流、var、HTTP 客户端)则完全向后兼容,可渐进采用。

参考资料