Skip to content

JDK 11 新增特性、使用方法与迁移实战

JDK 11是JDK采用六个月发布节奏后的第一个重要LTS之一。从JDK 8升级到11,不只是获得11当期API,还会跨过JDK 9的模块系统和JDK 10的局部变量类型推断。因此本文把“9~11累计可用能力”和“JDK 11首次新增”分开说明。

一、从JDK 8到11获得什么

首次版本能力生产价值
JDK 9JPMS模块系统明确模块依赖和封装边界
JDK 9JShell快速验证语法和API
JDK 9List.of等集合工厂简洁创建不可变集合
JDK 9Stream和Optional增强截断、空流、缺值组合
JDK 9接口私有方法复用默认方法内部逻辑
JDK 9G1成为常见服务器默认GC默认GC和日志发生变化
JDK 10局部变量var减少明显类型的重复书写
JDK 10不可变集合复制和Collector防御性复制
JDK 11标准HTTP ClientHTTP/1.1、HTTP/2、异步调用
JDK 11String、Files、Optional等API增强常用代码更简洁
JDK 11单文件源码运行脚本式Demo和工具
JDK 11JFR、TLS 1.3、ZGC实验版诊断、安全和低延迟探索
JDK 11Java EE/CORBA模块移除旧项目必须显式补依赖或迁移

二、JDK 11标准HTTP Client

JDK 11正式提供java.net.http.HttpClient,支持同步、异步、HTTP/2、WebSocket和连接复用。

2.1 同步GET

java
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 异步调用

java
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增强

java
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

java
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.isSameFilemismatch等API可用于文件判断,其中Files.mismatch是JDK 12新增,不能误写为JDK 11能力。

五、Optional、Predicate和Collection增强

Optional.isEmpty

java
if (user.isEmpty()) {
    throw new NotFoundException("用户不存在");
}

JDK 11新增Optional.isEmpty(),语义上等于!isPresent()

Predicate.not

java
List<String> values = lines
        .filter(Predicate.not(String::isBlank))
        .collect(Collectors.toList());

Collection.toArray

java
String[] names = nameList.toArray(String[]::new);

方法引用写法避免手工创建零长度数组,也保留准确数组类型。

六、单文件源码运行

文件Hello.java

java
public class Hello {
    public static void main(String[] args) {
        System.out.println("hello, " + args[0]);
    }
}

直接运行:

bash
java Hello.java Java11

它适合教学、诊断脚本和小工具,不替代Maven/Gradle工程。源码仍会被编译执行,只是编译中间步骤由java启动器处理;复杂依赖、测试、打包和可重复构建仍应使用构建工具。

七、Lambda参数使用var

局部变量var来自JDK 10;JDK 11允许Lambda形参使用var

java
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后可使用:

java
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会建立不可变快照语义。

java
List<String> snapshot = List.copyOf(mutableList);

List.copyOf来自JDK 10。

九、JDK 9 Stream和Optional增强

takeWhile与dropWhile

java
List<Integer> prefix = Stream.of(2, 4, 6, 7, 8)
        .takeWhile(value -> value % 2 == 0)
        .collect(Collectors.toList()); // 2,4,6

takeWhile只取有序流中从开头连续满足的前缀,不等同于filter;遇到7后停止,不会继续取后面的8。

Stream.ofNullable

java
Stream<String> stream = Stream.ofNullable(possibleNull);

Optional.stream

java
List<User> users = ids.stream()
        .map(repository::findById)
        .flatMap(Optional::stream)
        .collect(Collectors.toList());

Optional.ifPresentOrElse和or

java
user.ifPresentOrElse(this::sendMessage, this::recordMissing);

Optional<User> result = cache.find(id)
        .or(() -> database.find(id));

十、接口私有方法

JDK 8默认方法之间的公共逻辑只能暴露为public方法或移到工具类;JDK 9允许接口定义私有实例/静态方法:

java
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。模块描述文件:

java
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

text
$ jshell
jshell> var names = List.of("Tom", "Amy")
jshell> names.stream().map(String::toUpperCase).toList()

注意:示例中的Stream.toList()是JDK 16能力,如果使用真正的JDK 11 JShell,应写:

text
jshell> names.stream().map(String::toUpperCase).collect(Collectors.toList())

JShell适合验证API、表达式和小段算法,不代替单元测试和可重复构建。

十三、运行时与JVM变化

G1成为常见默认GC

JDK 9起,常见服务器配置的默认GC从Parallel转为G1。升级后即使未配置GC参数,吞吐、停顿、堆布局和日志都可能变化,必须重新建立性能基线。

统一JVM日志

JDK 8常见GC参数:

bash
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log

JDK 11使用统一日志框架:

bash
-Xlog:gc*,safepoint:file=/logs/gc.log:time,uptime,level,tags

Flight Recorder

JFR在OpenJDK 11中成为重要低开销诊断能力,可记录CPU、分配、锁、线程、IO和GC事件:

bash
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或编译失败。

处理方式:

  1. 搜索javax.xml.bindjavax.xml.ws、CORBA等引用。
  2. 确认业务是否仍需要。
  3. 需要则显式引入兼容依赖并测试。
  4. 不要把“JDK 11移除Java EE模块”和“Spring Boot 3迁移到jakarta包名”混为一谈,它们是不同阶段的问题。

十五、其他重要变化

能力说明
Nest-Based Access Control嵌套类通过Nestmate模型访问私有成员,减少编译器桥接方法
动态Class-File常量支持CONSTANT_Dynamic,主要影响语言和框架实现者
Flight RecorderOpenJDK中的生产诊断能力
Nashorn废弃JDK 11标记废弃,后续版本移除
JavaFX分离不再随常见JDK发行版内置,需要独立依赖和打包

十六、JDK 8升级到11 Runbook

mermaid
flowchart TD
    A["盘点依赖、插件、JVM参数"] --> B["升级Maven或Gradle构建链"]
    B --> C["用jdeps和测试发现内部API"]
    C --> D["补齐移除模块的显式依赖"]
    D --> E["转换GC日志和失效参数"]
    E --> F["验证TLS、Agent和反射"]
    F --> G["功能、性能、灰度和回滚"]

常用证据:

bash
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-infoClasspath项目可以继续运行,但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兼容。