JDK 11 新增特性、使用方法与迁移实战
JDK 11是JDK采用六个月发布节奏后的第一个重要LTS之一。从JDK 8升级到11,不只是获得11当期API,还会跨过JDK 9的模块系统和JDK 10的局部变量类型推断。因此本文把“9~11累计可用能力”和“JDK 11首次新增”分开说明。
一、从JDK 8到11获得什么
| 首次版本 | 能力 | 生产价值 |
|---|---|---|
| JDK 9 | JPMS模块系统 | 明确模块依赖和封装边界 |
| JDK 9 | JShell | 快速验证语法和API |
| JDK 9 | List.of等集合工厂 | 简洁创建不可变集合 |
| JDK 9 | Stream和Optional增强 | 截断、空流、缺值组合 |
| JDK 9 | 接口私有方法 | 复用默认方法内部逻辑 |
| JDK 9 | G1成为常见服务器默认GC | 默认GC和日志发生变化 |
| JDK 10 | 局部变量var | 减少明显类型的重复书写 |
| JDK 10 | 不可变集合复制和Collector | 防御性复制 |
| JDK 11 | 标准HTTP Client | HTTP/1.1、HTTP/2、异步调用 |
| JDK 11 | String、Files、Optional等API增强 | 常用代码更简洁 |
| JDK 11 | 单文件源码运行 | 脚本式Demo和工具 |
| JDK 11 | JFR、TLS 1.3、ZGC实验版 | 诊断、安全和低延迟探索 |
| JDK 11 | Java EE/CORBA模块移除 | 旧项目必须显式补依赖或迁移 |
二、JDK 11标准HTTP Client
JDK 11正式提供java.net.http.HttpClient,支持同步、异步、HTTP/2、WebSocket和连接复用。
2.1 同步GET
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(2))
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/orders/1001"))
.timeout(Duration.ofSeconds(3))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() / 100 != 2) {
throw new IllegalStateException("下游返回异常:" + response.statusCode());
}connectTimeout控制建立连接,HttpRequest.timeout控制请求级时限。业务还要考虑DNS、TLS、读取、重试和入口总时限,不能只配置一个超时就认为完整。
2.2 异步调用
CompletableFuture<OrderResponse> future = client
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() / 100 != 2) {
throw new CompletionException(
new IllegalStateException("HTTP " + response.statusCode()));
}
return parseOrder(response.body());
});生产注意:
- 复用
HttpClient,不要每次请求都创建。 - 明确代理、TLS、重定向和认证策略。
- HTTP状态非2xx不会自动抛业务异常,要主动判断。
- 异步返回CompletableFuture,不表示业务无限并发安全。
- 重试必须限制次数、退避、抖动和幂等范围。
- 连接池细节和可观测能力与第三方客户端不同,选型要结合框架生态。
三、String API增强
String input = " \n订单1001\t ";
boolean blank = input.isBlank();
String stripped = input.strip();
String left = input.stripLeading();
String right = input.stripTrailing();
String repeated = "-".repeat(20);
long lineCount = "a\nb\nc".lines().count();| 方法 | 与旧方法区别 |
|---|---|
isBlank | 空串或只含Unicode空白都返回true |
strip | 按Unicode空白处理;trim主要处理较小范围字符 |
lines | 惰性返回行Stream,更准确处理行终止符 |
repeat | 重复字符串指定次数 |
不要把isBlank用于所有业务校验。用户名、编码、密码是否允许某类空白仍应由明确业务规则决定。
四、Files和Path便捷API
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Path file = Path.of("config", "rules.json");
String json = Files.readString(file, StandardCharsets.UTF_8);
Files.writeString(file, json, StandardCharsets.UTF_8);这些方法适合小型文本文件。readString会把整个文件读入内存,不适合大型日志、导入文件或用户上传内容;大文件应使用BufferedReader、Stream或分块处理,并限制文件大小。
Files.isSameFile、mismatch等API可用于文件判断,其中Files.mismatch是JDK 12新增,不能误写为JDK 11能力。
五、Optional、Predicate和Collection增强
Optional.isEmpty
if (user.isEmpty()) {
throw new NotFoundException("用户不存在");
}JDK 11新增Optional.isEmpty(),语义上等于!isPresent()。
Predicate.not
List<String> values = lines
.filter(Predicate.not(String::isBlank))
.collect(Collectors.toList());Collection.toArray
String[] names = nameList.toArray(String[]::new);方法引用写法避免手工创建零长度数组,也保留准确数组类型。
六、单文件源码运行
文件Hello.java:
public class Hello {
public static void main(String[] args) {
System.out.println("hello, " + args[0]);
}
}直接运行:
java Hello.java Java11它适合教学、诊断脚本和小工具,不替代Maven/Gradle工程。源码仍会被编译执行,只是编译中间步骤由java启动器处理;复杂依赖、测试、打包和可重复构建仍应使用构建工具。
七、Lambda参数使用var
局部变量var来自JDK 10;JDK 11允许Lambda形参使用var:
BiFunction<String, String, String> join =
(@NotNull var left, @NotNull var right) -> left + right;主要价值是给Lambda参数添加类型注解。规则:
- 同一个Lambda的参数要么全部使用
var,要么都不使用。 - 不能混用
var left, String right。 var不是动态类型,编译期类型仍然确定。var只能用于有初始化器的局部变量和允许的Lambda形参位置,不能用于字段、方法返回类型。
八、JDK 9集合工厂
升级到JDK 11后可使用:
List<String> roles = List.of("ADMIN", "AUDITOR");
Set<String> channels = Set.of("APP", "WEB");
Map<String, Integer> weights = Map.of("HIGH", 100, "NORMAL", 50);这些集合:
- 不允许结构修改。
- 不接受null元素或null键值。
- 不承诺具体实现类。
- Set和Map迭代顺序不能作为业务契约。
它们不是Collections.unmodifiableList(original)的简单同义词:后者只是不可通过包装视图修改,原集合变化仍可能反映到视图;List.copyOf会建立不可变快照语义。
List<String> snapshot = List.copyOf(mutableList);List.copyOf来自JDK 10。
九、JDK 9 Stream和Optional增强
takeWhile与dropWhile
List<Integer> prefix = Stream.of(2, 4, 6, 7, 8)
.takeWhile(value -> value % 2 == 0)
.collect(Collectors.toList()); // 2,4,6takeWhile只取有序流中从开头连续满足的前缀,不等同于filter;遇到7后停止,不会继续取后面的8。
Stream.ofNullable
Stream<String> stream = Stream.ofNullable(possibleNull);Optional.stream
List<User> users = ids.stream()
.map(repository::findById)
.flatMap(Optional::stream)
.collect(Collectors.toList());Optional.ifPresentOrElse和or
user.ifPresentOrElse(this::sendMessage, this::recordMissing);
Optional<User> result = cache.find(id)
.or(() -> database.find(id));十、接口私有方法
JDK 8默认方法之间的公共逻辑只能暴露为public方法或移到工具类;JDK 9允许接口定义私有实例/静态方法:
interface AuditSupport {
default void auditCreate(String id) {
write("CREATE", id);
}
default void auditDelete(String id) {
write("DELETE", id);
}
private void write(String action, String id) {
System.out.println(action + ":" + id);
}
}私有接口方法只服务于接口内部实现复用,不能被实现类调用或覆盖。
十一、JPMS模块系统
JDK 9引入Java Platform Module System。模块描述文件:
module com.example.order {
requires java.net.http;
exports com.example.order.api;
opens com.example.order.dto to com.fasterxml.jackson.databind;
}| 关键字 | 含义 |
|---|---|
requires | 读取另一个模块 |
exports | 向其他模块公开包的public API |
opens | 允许目标模块进行深反射 |
uses | 声明使用某个服务接口 |
provides ... with | 声明服务实现 |
Classpath项目在JDK 11仍可以不立即模块化,代码通常进入Unnamed Module。但JDK自身已经模块化,旧代码访问JDK内部API或依赖曾经内置的Java EE模块时会暴露问题。
不要一遇到反射问题就永久添加--add-opens。正确顺序是升级框架、改用公开API、确认必要开放范围,再把--add-opens作为有下线计划的临时兼容方案。
十二、JShell
$ jshell
jshell> var names = List.of("Tom", "Amy")
jshell> names.stream().map(String::toUpperCase).toList()注意:示例中的Stream.toList()是JDK 16能力,如果使用真正的JDK 11 JShell,应写:
jshell> names.stream().map(String::toUpperCase).collect(Collectors.toList())JShell适合验证API、表达式和小段算法,不代替单元测试和可重复构建。
十三、运行时与JVM变化
G1成为常见默认GC
JDK 9起,常见服务器配置的默认GC从Parallel转为G1。升级后即使未配置GC参数,吞吐、停顿、堆布局和日志都可能变化,必须重新建立性能基线。
统一JVM日志
JDK 8常见GC参数:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.logJDK 11使用统一日志框架:
-Xlog:gc*,safepoint:file=/logs/gc.log:time,uptime,level,tagsFlight Recorder
JFR在OpenJDK 11中成为重要低开销诊断能力,可记录CPU、分配、锁、线程、IO和GC事件:
jcmd <pid> JFR.start name=incident settings=profile duration=5m filename=incident.jfr实际生产采集前评估事件配置、持续时间、磁盘和敏感信息。
TLS 1.3
JDK 11实现TLS 1.3。升级可能改善安全和握手能力,也可能暴露旧代理、旧证书或老服务协议兼容问题。不能通过长期启用弱协议解决,应升级对端和证书体系。
ZGC和Epsilon
JDK 11引入实验性ZGC和Epsilon GC。目标JDK 11中的ZGC不能按后续版本的成熟度和平台支持来描述;生产是否可用必须看精确发行版和支持策略。Epsilon只分配不回收,适合性能实验和短生命周期任务,不适合普通长时间服务。
十四、移除Java EE和CORBA模块
JDK 11移除了JDK 9已标记废弃的一批模块,例如部分JAXB、JAX-WS、Activation、CORBA能力。JDK 8项目可能因为“JDK自带”而没有声明依赖,升级后出现ClassNotFoundException或编译失败。
处理方式:
- 搜索
javax.xml.bind、javax.xml.ws、CORBA等引用。 - 确认业务是否仍需要。
- 需要则显式引入兼容依赖并测试。
- 不要把“JDK 11移除Java EE模块”和“Spring Boot 3迁移到jakarta包名”混为一谈,它们是不同阶段的问题。
十五、其他重要变化
| 能力 | 说明 |
|---|---|
| Nest-Based Access Control | 嵌套类通过Nestmate模型访问私有成员,减少编译器桥接方法 |
| 动态Class-File常量 | 支持CONSTANT_Dynamic,主要影响语言和框架实现者 |
| Flight Recorder | OpenJDK中的生产诊断能力 |
| Nashorn废弃 | JDK 11标记废弃,后续版本移除 |
| JavaFX分离 | 不再随常见JDK发行版内置,需要独立依赖和打包 |
十六、JDK 8升级到11 Runbook
flowchart TD
A["盘点依赖、插件、JVM参数"] --> B["升级Maven或Gradle构建链"]
B --> C["用jdeps和测试发现内部API"]
C --> D["补齐移除模块的显式依赖"]
D --> E["转换GC日志和失效参数"]
E --> F["验证TLS、Agent和反射"]
F --> G["功能、性能、灰度和回滚"]常用证据:
java -version
javac -version
mvn -v
jdeps --jdk-internals app.jar
java -Xlog:gc* -version迁移重点:
- Maven Compiler、Surefire、JaCoCo和打包插件。
- Lombok、MapStruct等Annotation Processor。
- Spring、Hibernate、数据库驱动和日志组件。
- APM、安全、Profiler和热部署Agent。
- JAXB/JAX-WS、JavaFX、Nashorn和内部API。
- GC参数、统一日志和默认G1。
- Docker基础镜像、字体、时区、CA证书。
十七、常见误区
| 误区 | 正确理解 |
|---|---|
var是JDK 11新增 | 局部变量var来自JDK 10,JDK 11增强Lambda形参 |
List.of是JDK 11新增 | 来自JDK 9 |
| JDK 11必须使用module-info | Classpath项目可以继续运行,但JDK本身已模块化 |
List.of等于Arrays.asList | 前者不可修改且不允许null,语义不同 |
| HttpClient非2xx自动抛异常 | 需要应用检查statusCode |
| 单文件运行等于解释执行 | 源码仍会被编译后执行 |
| 升级只需要改JAVA_HOME | 依赖、插件、参数、TLS、Agent都可能受影响 |
十八、面试回答
JDK 11主要新增了什么
JDK 11当期重点包括标准HTTP Client、String的isBlank/strip/lines/repeat、Files.readString/writeString、Optional.isEmpty、Predicate.not、单文件源码运行、Lambda参数var,以及JFR、TLS 1.3等运行时增强。若从JDK 8升级,还会累计获得JDK 9的模块系统、集合工厂、Stream/Optional增强和JDK 10的局部变量var。JDK 11同时移除了部分Java EE/CORBA模块,升级要补显式依赖并处理旧反射和JVM参数。
JDK 8升级11最容易出什么问题
常见是构建插件和Agent过旧、JAXB等原JDK内置模块缺失、访问JDK内部API、GC参数不兼容、默认GC变化、TLS或证书兼容、容器镜像差异。应使用jdeps、完整测试和性能基线逐项验证。
十九、关联学习
| 专题 | 内容 |
|---|---|
| LTS版本演进总览 | 四个版本横向比较 |
| JDK 8新增特性 | 升级起点和兼容写法 |
| JDK 17新增特性 | 从11继续升级到17 |
| 模块与反射 | 强封装和深反射 |
| JVM调优 | GC、JFR和运行参数 |
本章小结
JDK 11的核心不只是多几个String方法,而是Java进入模块化、快速发布和现代运行时阶段。业务开发要会标准HTTP Client和便捷API,工程升级则必须处理移除模块、内部API、构建插件、GC日志、TLS和Agent兼容。
