Java 11 全面介绍:拥抱新时代的长效支持版本
Java 平台自诞生以来,一直是企业级应用开发、大数据处理、Android 应用以及众多其他领域的基石。随着技术的发展和用户需求的变化,Java 平台本身也在不断演进。Oracle 在采纳了新的发布节奏后,于 2018 年 9 月发布了 Java 11,这是继 Java 8 之后的第二个长期支持(LTS)版本。与每六个月发布一个新版本的策略并行,LTS 版本提供了更长时间的官方支持和维护,使其成为许多企业和开发者优先选择的升级目标。
Java 11 带来了大量新特性、改进和移除,不仅提升了开发效率和代码的可读性,还在性能、安全性和诊断工具方面迈出了重要步伐。本文将对 Java 11 的主要特性进行全面而深入的介绍,帮助开发者理解这些变化,并为升级或使用 Java 11 提供参考。
一、 Java 11 的重要地位:长期支持(LTS)版本
在深入探讨具体特性之前,理解 Java 11 的定位至关重要。Oracle 自 Java 9 起,将 Java SE 的发布周期调整为每六个月一个新版本。这种快速迭代的模式使得新功能可以更快地触达开发者,但同时也意味着非 LTS 版本(如 Java 9、10、12、13、14、15、16、17…)的支持周期相对较短(通常只有六个月,直到下一个版本发布)。
相比之下,LTS 版本(目前是 Java 8, 11, 17, 21…)则提供多年(通常是三年或更长,取决于具体的提供商)的官方补丁更新和安全修复。对于大型企业应用、对稳定性要求极高的系统或那些无法频繁升级的项目来说,选择一个 LTS 版本进行开发和生产部署是明智的决策。Java 11 作为当时最新的 LTS 版本,自然成为了许多项目从 Java 8 或更早版本迁移的首选目标。
升级到 Java 11 不仅仅是为了获得新特性,更是为了获取持续的安全保障、性能优化以及对现代硬件和操作系统的良好支持。虽然快速发布版本引入了许多令人兴奋的实验性或早期功能,但 LTS 版本代表了平台在该时间点上的成熟和稳定状态。
接下来,我们将详细介绍 Java 11 中那些最具代表性和影响力的主要特性。
二、 主要特性详解
Java 11 引入了多个重要的 JEPs (JDK Enhancement Proposals),其中一些对开发者日常编码体验产生了直接影响,而另一些则在底层优化、诊断工具或模块化策略上带来了重大改变。
2.1 全新的 HTTP 客户端 API (JEP 321)
这是 Java 11 中最受关注的特性之一,也是从孵化状态转为标准 API 的一个重要里程碑(它首次在 Java 9 中作为孵化模块 jdk.incubator.httpclient
出现,并在 Java 10 中得到更新)。Java 11 的 HTTP Client API (java.net.http
) 提供了一种现代的、易于使用的、支持同步和异步操作的 HTTP 客户端实现。
为什么需要新的 HTTP 客户端?
Java 标准库中原有的 HttpURLConnection
类功能简单且使用起来相对繁琐,缺乏对现代 HTTP 特性(如 HTTP/2、WebSockets)的良好支持,且默认是同步阻塞的。随着微服务、RESTful API 调用等变得越来越普遍,一个功能强大、易于编程且高性能的 HTTP 客户端变得必不可少。
新 API 的特点:
- 支持 HTTP/1.1 和 HTTP/2: 原生支持更高效的 HTTP/2 协议。
- 同步和异步模式: 可以方便地进行阻塞或非阻塞的请求。异步操作基于
CompletableFuture
,这使得处理并发 HTTP 请求变得非常优雅。 - 易于使用的 Builder 模式: 通过
HttpClient.newBuilder()
和HttpRequest.newBuilder()
可以方便地配置客户端和请求的各种属性(如请求方法、头部、超时、代理等)。 - 支持 WebSocket: 虽然在 Java 11 中是初步支持,但为未来的完整 WebSocket 支持奠定了基础。
- 响应体处理器 (Body Handlers): 提供灵活的方式来处理响应体,例如直接将响应体读取为字符串、字节数组、文件,或者使用自定义的处理器。
代码示例:同步 GET 请求
“`java
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class SyncHttpClientExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1) // 可选,指定HTTP版本
.followRedirects(HttpClient.Redirect.NORMAL) // 可选,处理重定向
.connectTimeout(Duration.ofSeconds(10)) // 可选,设置连接超时
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com"))
.timeout(Duration.ofSeconds(20)) // 可选,设置请求超时
.header("Content-Type", "application/json") // 可选,添加请求头
.GET() // 指定GET方法,默认为GET
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body:\n" + response.body());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
“`
代码示例:异步 GET 请求
“`java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class AsyncHttpClientExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient(); // 简单方式创建客户端
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com/async"))
.GET()
.build();
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// 处理异步结果
responseFuture.thenAccept(response -> {
System.out.println("Async Status Code: " + response.statusCode());
System.out.println("Async Response Body:\n" + response.body());
}).exceptionally(e -> {
System.err.println("Async request failed: " + e.getMessage());
return null; // Consumes the exception
}).join(); // 等待异步操作完成(在实际应用中,你可能不会在这里join,而是让事件循环或线程池处理)
System.out.println("Main thread finished.");
}
}
“`
新的 HTTP Client API 极大地简化了 HTTP 请求的编写,提供了现代化的异步编程模型,并且是 Java 标准库的一部分,无需依赖第三方库(如 Apache HttpClient, OkHttp 等),这对于许多应用来说是一个重要的改进。
2.2 Lambda 参数的局部变量语法 var
(JEP 323)
Java 10 引入了局部变量类型推断 var
,极大地提高了局部变量声明的简洁性。Java 11 将 var
的使用范围扩展到了 Lambda 表达式的参数列表。
为什么需要 var
在 Lambda 参数中?
在 Java 8 中引入 Lambda 表达式时,其参数列表可以显式声明类型 ((String s) -> s.length()
),也可以省略类型让编译器推断 (s -> s.length()
)。然而,如果你想给 Lambda 参数添加注解(如 @Nullable
, @Nonnull
)或者 final
修饰符,你就 必须 显式声明类型,即使类型是可以推断的。
例如,你不能这样做:(@Nonnull s) -> s.length()
或 (final s) -> s.length()
。你必须写成 (@Nonnull String s) -> s.length()
。
引入 var
在 Lambda 参数中解决了这个问题。现在你可以使用 var
来代替显式类型名,同时保留添加注解或 final
的能力。
语法:
(var param1, var param2) -> { ... }
请注意,一旦你在 Lambda 参数列表中的 任何 参数上使用了 var
,你就必须在 所有 参数上使用 var
。你不能混合使用 var
和显式类型。
代码示例:
“`java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
// 假设有一个 @Nullable 注解
import javax.annotation.Nullable; // 需要javax.annotation-api依赖
public class VarInLambdaExample {
public static void main(String[] args) {
List
// 传统方式,需要显式类型来添加注解
names.stream()
.filter((@Nullable String name) -> name != null)
.map(String::toUpperCase)
.forEach(System.out::println);
// 输出: ALICE BOB CHARLIE
System.out.println("--- Using var ---");
// 使用 var,可以在保留类型推断的同时添加注解
names.stream()
.filter((@Nullable var name) -> name != null) // 更简洁
.map((var name) -> name.toUpperCase()) // 也可以用于不带注解的参数,但通常没必要
.forEach(System.out::println);
// 输出: ALICE BOB CHARLIE
// 另一个例子:final 参数
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.map((final var x) -> x * 2) // 可以使用 final var
.forEach(System.out::println);
// 输出: 2 4 6
}
}
“`
尽管对于不添加注解或 final
的简单 Lambda 表达式来说,s -> ...
仍然是最简洁的方式,但 var
在 Lambda 参数中的引入填补了一个空白,使得在需要注解或 final
时能够保持一定程度的简洁性和一致性。
2.3 启动单文件源代码程序 (JEP 330)
这个特性极大地简化了小型 Java 程序的执行流程,尤其是对于初学者、编写脚本或快速测试片段的场景。
传统方式:
编写一个简单的 HelloWorld.java
,你需要先使用 javac
命令编译它:
bash
javac HelloWorld.java
然后使用 java
命令运行编译后的 .class
文件:
bash
java HelloWorld
这对于只有一个文件的简单程序来说感觉有点繁琐。
Java 11 的改变:
Java 11 允许你直接使用 java
命令运行一个 .java
源代码文件,而无需显式地进行编译步骤。
语法:
bash
java YourProgram.java
当你在命令行执行 java YourProgram.java
时,JDK 内部会透明地进行编译(将源代码编译到内存中,而不是生成 .class
文件),然后立即执行编译后的代码。
代码示例:
假设你有一个名为 Greeting.java
的文件:
java
public class Greeting {
public static void main(String[] args) {
if (args.length > 0) {
System.out.println("Hello, " + args[0] + "!");
} else {
System.out.println("Hello, World!");
}
}
}
在 Java 11 或更高版本中,你可以直接在命令行执行:
bash
java Greeting.java John
输出:
Hello, John!
或者
bash
java Greeting.java
输出:
Hello, World!
限制:
这个特性主要设计用于单文件程序。如果你的程序依赖于其他 .java
文件、JAR 包或资源文件,或者需要更复杂的构建过程,仍然需要使用 javac
、jar
、构建工具(Maven/Gradle)等传统方式。它不适用于包含多个源文件、使用包声明或需要特定 classpath 设置的项目。
尽管有这些限制,对于编写简单的实用工具、教学示例或快速原型验证,这个特性提供了极大的便利。
2.4 Z Garbage Collector (ZGC) (JEP 333) – 实验性
ZGC 是 Java 11 中引入的一个新的、实验性的垃圾收集器。其主要目标是实现极低的暂停时间(<10ms),即使在管理非常大的堆(几 TB)时也能保持高效。
为什么需要 ZGC?
传统的垃圾收集器,如 ParallelGC 或 CMS,在高吞吐量的同时,可能会在进行”Stop-The-World”(STW)阶段时引入较长的暂停时间。即使是 G1 垃圾收集器,虽然旨在限制暂停时间,但在处理非常大的堆或特定工作负载时,仍然可能出现不可接受的长暂停。
对于许多现代应用,尤其是内存数据库、缓存系统、实时交易系统等,对延迟的要求非常高。任何显著的 STW 暂停都可能直接影响用户体验或服务可用性。ZGC 正是为了满足这类对低延迟有极端要求的场景而设计的。
ZGC 的核心理念和特点:
- 并发执行: ZGC 尽可能多地与应用线程并发执行其工作(标记、整理、引用更新等)。STW 阶段非常短暂,主要用于初始标记和少数关键的根扫描。
- 基于区域 (Region-based): 类似于 G1,ZGC 将堆划分为不同大小的区域(Page)。
- 彩色指针 (Colored Pointers): ZGC 使用指针中的额外位来存储关于对象状态的信息(如标记颜色),从而减少对对象头部的修改需求,并支持并发处理。这需要 64 位系统和对指针的特殊处理。
- 单代 (Single-Generation): 与许多分代垃圾收集器(新生代、老年代)不同,ZGC 是一个单代收集器。
- 低延迟,高吞吐量: ZGC 的主要目标是低延迟,但也努力维持高吞吐量。
如何启用 (实验性):
在 Java 11 中,ZGC 仍然是实验性的,需要使用特定的 JVM 参数启用:
bash
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC YourProgram
影响和意义:
ZGC 的引入是 JVM 垃圾收集器发展的一个重要方向,它挑战了传统 GC 的设计模式,并为那些对延迟极其敏感的应用提供了新的选择。虽然在 Java 11 中是实验性的,但在后续版本中,ZGC 得到了持续的改进和优化,并在 Java 15 中成为了非实验性功能,进一步成熟。对于需要管理超大内存且要求极低暂停时间的应用来说,ZGC 是一个极具吸引力的选择。
2.5 开源 Java Flight Recorder (JFR) (JEP 328)
Java Flight Recorder (JFR) 是一个强大的、用于收集 Java 应用程序运行时数据的分析工具。它能够以极低的开销记录大量的事件,如线程活动、锁竞争、垃圾收集、I/O 操作、方法调用等。Java Mission Control (JMC) 是一个配套的客户端工具,用于可视化和分析 JFR 记录生成的数据。
Java 11 之前的状态:
在 Java 11 之前,JFR 和 JMC 是 Oracle JDK 的商业特性,需要在生产环境中使用商业许可证才能使用。这限制了许多组织在生产环境中利用 JFR 进行性能分析和故障排除的能力。
Java 11 的改变:
Java 11 将 JFR 作为 OpenJDK 项目的一部分开源,使其可以免费用于生产环境。
如何启用和使用:
在 Java 11+ 中,你不再需要 -XX:+UnlockCommercialFeatures
参数来启用 JFR。你可以直接使用标准 JVM 参数:
- 启动时开始记录:
bash
java -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr YourProgram
这个命令会在程序启动后开始记录,持续 60 秒,并将结果保存到myrecording.jfr
文件中。 - 在运行时通过 JCMD 触发:
首先找到目标 Java 进程的 PID:
bash
jcmd
然后开始记录:
bash
jcmd <pid> JFR.start duration=60s filename=myrecording.jfr
停止记录:
bash
jcmd <pid> JFR.stop name=<recording_name>
(这里的<recording_name>
是启动时指定的名称,如果未指定则有一个默认名称)。
影响和意义:
JFR 的开源是 Java 平台诊断能力方面的一个重大进步。它为所有开发者提供了强大的生产环境分析工具,帮助快速定位性能瓶颈、内存泄漏、线程问题等。结合开源的 JMC(同样在 Java 11 发布前后开源),开发者现在可以免费地利用这些曾经是商业级的工具来提高应用的可靠性和性能。
2.6 移除 Java EE 和 CORBA 模块 (JEP 320)
这是 Java 11 中一个影响兼容性的重要变化。Java 平台长期以来包含了一些与 Java EE (现称 Jakarta EE) 和 CORBA 相关的模块。然而,这些技术已经有了独立的生命周期和发展方向,或者已经过时。将它们保留在核心 Java SE 平台中增加了 JDK 的体积和维护负担。
被移除的模块:
Java 11 中彻底移除了以下模块:
java.se.ee
:元模块,聚合了所有 EE 相关的模块。java.corba
:CORBA (Common Object Request Broker Architecture)。java.transaction
:JTA (Java Transaction API)。java.activation
:JAF (Java Activation Framework)。java.xml.ws
:JAX-WS (Java API for XML Web Services)。java.xml.bind
:JAXB (Java Architecture for XML Binding)。java.rmi.CORBA
:RMI-IIOP (RMI over IIOP)。
影响:
如果你的应用直接使用了这些模块中提供的 API(例如,使用 JAXB 进行 XML 序列化/反序列化,使用 JAX-WS 调用 Web Service),那么直接将 JDK 升级到 Java 11 会导致编译或运行时错误(java.lang.ClassNotFoundException
或 java.lang.NoClassDefFoundError
等)。
如何解决:
这些技术本身并没有消失。它们现在作为独立的库在 Maven Central 等仓库中提供。要解决因移除这些模块导致的兼容性问题,你需要将相应的库作为项目的外部依赖引入。
例如:
- 对于 JAXB: 添加
javax.xml.bind:jaxb-api
,以及一个 JAXB 运行时实现(如org.glassfish.jaxb:jaxb-runtime
)。 - 对于 JAX-WS: 添加
javax.xml.ws:jaxb-api
,以及一个实现(如com.sun.xml.ws:jaxws-rt
)。 - 对于 JTA: 添加
javax.transaction:jta
。 - 对于 JAF: 添加
javax.activation:activation
。 - 对于 CORBA: 如果还在使用,需要寻找第三方库(但通常建议迁移到更现代的技术)。
意义:
这一移除是 Java 平台模块化(Project Jigsaw,Java 9 引入)战略的延续,旨在精简核心 JDK,使其更加轻量级和可维护。这标志着核心 Java SE 与 Java EE 技术栈的进一步分离,鼓励开发者通过标准的依赖管理工具来管理这些非核心库。对于依赖这些旧技术的应用来说,升级到 Java 11 需要额外的迁移工作。
三、 其他值得关注的特性和变化
除了上述主要特性外,Java 11 还包含了一些其他重要的改进和底层变化:
- TLS 1.3 支持 (JEP 332): TLS 1.3 是最新版本的传输层安全协议,提供了更高的安全性和更好的性能(更少的握手往返)。Java 11 在
javax.net.ssl
API 中提供了对 TLS 1.3 的支持,并且默认启用。 - Unicode 10 支持 (JEP 327): 更新了对 Unicode Standard 10.0 的支持,包括新增的字符、块和脚本。
- Epsilon:一个 No-Op 垃圾收集器 (JEP 318): Epsilon 是一个实验性的 GC,它不做任何实际的垃圾回收工作。它的主要用途是性能测试,用于测量内存分配的开销,或者在已知不会产生垃圾且需要极低延迟的短生命周期应用中使用。使用参数
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
启用。 - 动态类文件常量 (JEP 309): 引入了新的常量池形式
CONSTANT_Dynamic
,这使得类文件的格式更加灵活,并为未来 JVM 特性(如值类型/Project Valhalla)奠定了基础。这是一个更底层的变化,对大多数开发者直接影响不大。 - 基于 Nest 的访问控制 (JEP 181): 引入了 “nests” 的概念,用于更好地建模 Java 语言中的嵌套类型(如内部类)。同一个 nest 中的类可以互相访问私有成员,而无需编译器生成桥接方法。这主要影响反射 API 和类文件的结构。
- 改进 Aarch64 Intrinsics (JEP 315): 提高了 AArch64 (ARM 64位) 架构上字符串和数组相关操作的性能。
- 丢弃 Nashorn JavaScript 引擎 (JEP 335) 和 Pack200 工具及 API (JEP 336): Nashorn 在 Java 15 中被彻底移除,Pack200 在 Java 14 中被移除。Java 11 标记了它们被废弃的开始。这反映了平台的发展方向,一些不再流行或有更好替代方案的技术被逐步移除。
四、 升级到 Java 11 的考量
迁移到 Java 11 LTS 版本通常是一个值得推荐的选择,可以获得性能、安全和新功能方面的优势。然而,升级过程可能会遇到一些挑战,主要集中在以下几个方面:
- 移除的 Java EE/CORBA 模块: 如果你的应用使用了 JAXB、JAX-WS、JTA、JAF 等 API,需要将相应的第三方库添加到项目的依赖中。这是最常见的兼容性问题。
- 模块化 (Project Jigsaw): Java 9 引入的模块化系统可能会影响依赖管理和 classpath 的设置。虽然许多传统应用可以在非模块化模式下运行(称为 “unnamed module”),但在某些情况下(例如,使用内部 JDK API),可能需要进行调整。Java 11 的移除模块进一步强调了模块化带来的变化。
- JVM 参数变化: 一些旧的 JVM 参数可能已被废弃或移除。在升级后,需要检查并更新应用的启动脚本。
- 垃圾收集器变化: 虽然 G1 是默认的 GC,但如果你使用了 CMS 或其他非 G1 GC,可能需要评估在 Java 11 中继续使用它们的影响(CMS 在 Java 14 中被移除),或者考虑切换到 G1 或 ZGC (实验性)。
- 第三方库兼容性: 确保你使用的所有第三方库都与 Java 11 兼容。大多数流行的库都会及时更新以支持新的 Java 版本,但对于较旧或不活跃维护的库,可能需要查找替代方案或等待更新。
尽管存在这些潜在的挑战,但通过周密的计划、充分的测试以及查阅官方文档和迁移指南,大多数应用都可以成功迁移到 Java 11。
五、 总结与展望
Java 11 是一个意义重大的 LTS 版本。它不仅带来了强大的新特性,如现代化的 HTTP Client API、Lambda 参数的 var
支持、便捷的单文件执行,更重要的是,它将 JFR 这样关键的诊断工具开源,并引入了革命性的低延迟垃圾收集器 ZGC(尽管是实验性)。同时,通过移除旧的 Java EE/CORBA 模块,Java 11 标志着核心平台更加精简和聚焦。
作为当时最新的 LTS 版本,Java 11 提供了一个稳定、安全且功能丰富的平台,非常适合作为企业应用新的基础版本。许多项目选择从 Java 8 直接跳跃到 Java 11,以获取显著的改进并获得长期的官方支持。
Java 的演进仍在继续,后续的 LTS 版本(如 Java 17, 21)在 Java 11 的基础上进一步发展,引入了更多令人兴奋的特性,如 Records、Sealed Classes、Pattern Matching 的改进、新的垃圾收集器等。但 Java 11 作为新发布周期下的首个 LTS 版本,在连接 Java 8 时代和未来 Java 版本之间起到了关键的桥梁作用。
拥抱 Java 11 意味着迈向一个更高效、更安全、更易于诊断和维护的 Java 开发新时代。理解并掌握其主要特性,对于任何希望保持技术领先地位的 Java 开发者和组织来说都至关重要。