Java 11是自Java 8后的首个长期支持版本,非常值得关注。在语法灵活性、标准库丰富度以及底层性能上进行了大量优化。
一、语言语法特性
1. Lambda 参数支持局部变量类型推断(var)
允许在隐式类型 Lambda 表达式的每个形参前使用保留类型名 var。在 Java 10 中,var 关键字被引入用于局部变量类型推断,但当时它不能用于 Lambda 表达式的参数,Java 11 将 var 的适用范围扩展到了 Lambda 参数,使得 var 在局部变量和 Lambda 参数中的使用风格保持统一。
为什么需要这个特性?
- 语法一致性:让局部变量(var x = ...)和 Lambda 形参((var x, var y) -> ...)的声明语法统一,减少认知负担。
- 支持注解:这是最实际的用途。在 Java 11 之前,你无法为隐式类型的 Lambda 形参添加注解(如 @Nullable、@Nonnull)。现在,通过 var,你可以像修饰普通局部变量一样,为 Lambda 形参添加上注解,从而利用类型检查器进行更严格的静态分析。
它的核心价值不在于"简化代码"(因为省略类型已经够简洁了),而在于为 Lambda 参数添加注解提供了一个优雅的语法位置,同时保持了 var 在整个 Java 语言中使用风格的一致性。编译器仍然是根据 Lambda 表达式的目标类型(函数式接口的泛型参数)来推断参数类型,var 本身不提供任何额外的类型信息。
(1)基本用法:
javascript
// 单参数
Consumer<String> consumer = (var s) -> System.out.println(s);
// 多参数
BinaryOperator<String> concat = (var s1, var s2) -> s1 + s2;
(2)结合注解使用
less
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
// 为 Lambda 参数添加注解
Function<String, Integer> length = (@NotNull var s) -> s.length();
// 多参数注解
Comparator<String> comparator = (@NotNull var a, @Nullable var b) -> a.compareTo(b);
(3)结合修饰符使用
sql
// 使用 final 修饰符
Function<Integer, Integer> square = (final var x) -> x * x;
使用规则与限制:
- var 仅适用于隐式类型 Lambda 表达式。对于显式类型 Lambda(如 (String s) -> ...),继续使用具体的类型名即可。
- var 不是关键字,而是一个保留类型名。这意味着你仍然可以将 var 用作变量名、方法名或包名,但不能将其用作类名或接口名。

二、新增API/包
1. 标准化 HTTP Client API (java.net.http)
标志着 Java 终于拥有了一个现代化的、内置的标准 HTTP 客户端,彻底告别了老旧且难用的 HttpURLConnection。在 Java 11 之前,Java 标准库中唯一的 HTTP 客户端是 HttpURLConnection,它存在诸多痛点:
- 仅支持阻塞模式:每个请求独占一个线程,无法适配高并发异步场景。
- 不支持 HTTP/2:无法享受多路复用、头部压缩等现代协议带来的性能提升。
- API 设计繁琐:手动管理输入输出流、连接断开等,极易导致连接泄漏。
- 功能缺失:没有内置的连接池管理、重定向策略等常用能力。
java.net.http 包定义了四大核心类型:
HttpClient:HTTP 客户端,跨请求的通用配置容器(协议版本、超时、代理、连接池等)。通过 HttpClient.newBuilder() 或 HttpClient.newHttpClient() 创建,一旦构建完成,就是不可变的,可用于发送多个请求。其 Builder 可以配置首选协议版本(HTTP/1.1 或 HTTP/2)、重定向策略、代理和认证器等。HttpRequest:HTTP 请求,通过 Builder 模式构建(URI、方法、请求头、请求体等)。通过 HttpRequest.newBuilder() 创建,可以设置 URI、请求方法(GET, POST 等)、请求体、超时时间和请求头。构建后也是不可变的。HttpResponse<T>:HTTP 响应,泛型T是响应体的类型,泛型参数由响应体处理器(BodyHandler)决定。它提供了 statusCode()、headers() 和 body() 等方法。WebSocket:WebSocket 客户端,支持双向实时通信。HttpRequest.BodyPublishers:提供多种请求体发布器(字符串、文件、字节数组等)。HttpResponse.BodyHandlers:提供多种响应体处理器(字符串、文件、字节数组、输入流等)。
(1)创建 HttpClient
java
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpClient.Redirect;
import java.net.ProxySelector;
import java.net.InetSocketAddress;
import java.time.Duration;
// 方式一:使用默认配置
HttpClient defaultClient = HttpClient.newHttpClient();
// 方式二:使用 Builder 进行定制
HttpClient customClient = HttpClient.newBuilder()
.version(Version.HTTP_2) // 首选 HTTP/2
.followRedirects(Redirect.NORMAL) // 允许正常重定向
.connectTimeout(Duration.ofSeconds(10)) // 连接超时10秒
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
.build();
(2)发送同步 GET 请求
java
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
// 1. 创建 HttpClient(可复用)
HttpClient client = HttpClient.newHttpClient();
// 2. 构建 HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.header("Accept", "application/json")
.GET() // 默认就是 GET,可以省略
.build();
// 3. 同步发送,阻塞等待响应,响应体作为 String 处理
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// 4. 处理响应
System.out.println("状态码: " + response.statusCode());
System.out.println("响应头: " + response.headers());
System.out.println("响应体: " + response.body());
(3)发送异步 GET 请求
arduino
// 3. 异步发送,sendAsync 返回 CompletableFuture,非阻塞
CompletableFuture<HttpResponse<String>> future = client
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// 链式处理响应
future.thenApply(HttpResponse::body) // 提取响应体
.thenAccept(System.out::println) // 消费响应体
.exceptionally(ex -> { // 异常处理
ex.printStackTrace();
return null;
})
.join(); // 等待异步流程完成
(4)发送 POST 请求(JSON)
swift
import java.net.http.HttpRequest.BodyPublishers;
String jsonBody = "{\"name\": \"Alice\", \"age\": 30}";
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(jsonBody)) // 请求体为 JSON 字符串
.build();
HttpResponse<String> postResponse = client.send(postRequest, BodyHandlers.ofString());
System.out.println("状态码: " + postResponse.statusCode());
System.out.println("响应体: " + postResponse.body());
(5)使用 WebSocket
typescript
WebSocket ws = client.newWebSocketBuilder()
.buildAsync(URI.create("wss://echo.websocket.org"), new WebSocket.Listener() {
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("WebSocket 已连接");
webSocket.sendText("Hello, WebSocket!", true);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("收到消息: " + data);
webSocket.request(1); // 请求下一条消息
return null;
}
})
.join(); // 等待连接建立
注意事项:
- 线程模型:异步任务的执行线程由 HttpClient 的 executor() 决定。如果未指定,将使用默认的执行器。由 CompletionStage 触发的依赖任务,如果未指定执行器,将在 CompletableFuture 的默认执行器或调用线程中运行。
- 不可变性:HttpClient 和 HttpRequest 实例在构建后都是不可变的,这意味着它们是线程安全的,可以在多个线程间安全地共享和复用。
- null 参数:除非特别说明,向该包中任何方法传递 null 参数都会导致 NullPointerException。
2. String 类实用方法增强
新增了6个高频使用的实例方法,覆盖了空白字符处理、多行文本分割和字符串重复拼接等常见场景,大幅减少了对外部工具类(如 Apache Commons Lang)的依赖。
- isBlank():判断字符串是否为空或仅包含空白字符,替代 str.trim().isEmpty(),且能识别 Unicode 空白字符。
- lines():按行终止符分割字符串,返回
Stream<String>,替代 BufferedReader 或 StringTokenizer。 - strip():去除首尾空白字符(Unicode 感知),替代 trim(),支持所有 Unicode 空白字符。
- stripLeading():仅去除开头空白字符。
- stripTrailing():仅去除末尾空白字符。
- repeat(int n):将字符串重复 n 次,替代 StringBuilder 循环拼接。
strip() 与 trim() 的关键区别:
trim() 只移除码点小于等于 U+0020 的字符(即 ASCII 空格),而 strip() 基于 Character.isWhitespace() 判断,能处理所有 Unicode 空白字符,包括全角空格(\u3000)、零宽空格(\u200B)、制表符等。
less
// trim() 无法去除全角空格(U+3000)
String s = "\u3000Hello\u3000";
System.out.println(s.trim().length()); // 输出: 7(全角空格仍在)
System.out.println(s.strip().length()); // 输出: 5(全角空格被去除)
3. Files 类文件读写简化
新增了readString() 和 writeString() 两个静态方法,将常见的文本文件读写操作简化为一行代码,内部自动处理资源的打开与关闭。
- readString(Path):读取文件全部内容为字符串(默认 UTF-8)。
- readString(Path, Charset):指定字符集读取文件全部内容。
- writeString(Path, CharSequence, OpenOption...):将字符串写入文件(默认 UTF-8,覆盖模式)。
- writeString(Path, CharSequence, Charset, OpenOption...):指定字符集、指定打开选项(如追加模式)写入文件。
java
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
public class FilesDemo {
public static void main(String[] args) throws IOException {
Path path = Paths.get("test.txt");
// 1. 一行读取文件为字符串(默认 UTF-8)
String content = Files.readString(path);
System.out.println(content);
// 2. 指定字符集读取
String utf16Content = Files.readString(path, StandardCharsets.UTF_16);
// 3. 一行写入字符串到文件(覆盖模式)
Files.writeString(path, "Hello, Java 11!", StandardCharsets.UTF_8);
// 4. 追加模式写入
Files.writeString(path, "追加内容\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND);
}
}
注意:
- readString() 会将整个文件加载到内存中,不适合处理超大文件。如果文件可能超过 2GB,应使用流式处理方式
4. Optional 类增强
新增了 isEmpty() 方法,提供了与 isPresent() 语义相反的直观判断方式,使空值检查的代码意图更加清晰。
scss
Optional<String> emptyOpt = Optional.empty();
Optional<String> nonEmptyOpt = Optional.of("Java 11");
// Java 11 之前:用 !isPresent() 判断为空
if (!emptyOpt.isPresent()) {
System.out.println("值为空");
}
// Java 11 之后:用 isEmpty() 更直观
if (emptyOpt.isEmpty()) {
System.out.println("值为空");
}
System.out.println(empty.isEmpty()); // true
System.out.println(nonEmptyOpt.isEmpty()); // false
5. 集合Collection 接口增强
新增了一个 toArray(IntFunction<T[]> generator) 默认方法,允许通过方法引用将集合转换为指定类型的数组,替代了旧版本中繁琐的 toArray(new T[0]) 写法。
ini
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectionDemo {
public static void main(String[] args) {
List<String> list = List.of("Java", "Python", "Go");
// Java 11 之前:需要传入一个预分配的数组
String[] array1 = list.toArray(new String[0]);
String[] array2 = list.toArray(new String[list.size()]);
// Java 11 之后:使用方法引用,更简洁且性能更优
String[] array3 = list.toArray(String[]::new);
// 或者
String[] array4 = list.toArray(size -> new String[size]);
// 嵌套集合场景下优势更明显
List<List<String>> nested = List.of(
List.of("A", "B"),
List.of("C", "D")
);
// 旧写法需要推断二维数组类型,比较繁琐
// 新写法简洁明了
String[][] matrix = nested.stream()
.map(inner -> inner.toArray(String[]::new))
.toArray(String[][]::new);
System.out.println(Arrays.deepToString(matrix));
// 输出: [[A, B], [C, D]]
}
}
6. Predicate 接口增强
新增了静态方法 not(),用于对给定的 Predicate 取反,同时保留方法引用的可读性。在 Java 11 之前,如果想对一个方法引用取反,必须写成 Lambda 表达式(如 person -> !person.isAdult()),无法直接对方法引用使用 .negate()。Java 11 的 Predicate.not() 解决了这个问题。
arduino
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateEnhancements {
public static void main(String[] args) {
List<String> items = Arrays.asList("Java", "", "Kotlin", " ", "Scala", "");
// ❌ Java 11 之前:无法直接对方法引用取反
// long nonEmpty = items.stream()
// .filter(String::isEmpty) // 只能筛选空的
// .count();
// 使用 Lambda 取反(可读性较差)
long nonEmpty1 = items.stream()
.filter(s -> !s.isEmpty())
.count();
// 场景1:过滤非空字符串
// ✅ Java 11:使用 Predicate.not() 对方法引用取反
long nonEmpty2 = items.stream()
.filter(Predicate.not(String::isEmpty))
.count();
System.out.println("非空字符串数量: " + nonEmpty2); // 3
// 结合 isBlank 使用
List<String> meaningful = items.stream()
.filter(Predicate.not(String::isBlank))
.collect(Collectors.toList());
System.out.println(meaningful); // [Java, Kotlin, Scala]
// 场景2:静态导入后更简洁
import static java.util.function.Predicate.not;
List<String> result = items.stream()
.filter(not(String::isEmpty))
.collect(Collectors.toList());
// 场景3:与其他默认方法组合使用
Predicate<String> isJava = "Java"::equals;
Predicate<String> isNotJava = Predicate.not(isJava);
System.out.println(isNotJava.test("Python")); // true
}
}
三、工具与底层运行
1. 基于嵌套的访问控制(Nest-Based Access Control)
让 JVM 层面的访问控制规则与 Java 语言层面的语义保持一致,消除编译器生成桥接方法的需要。
什么是桥接方法?
Java 语言从 1.1 版本开始就支持嵌套类(内部类),允许内部类直接访问外部类的私有成员。例如:
csharp
public class Outer {
private void outerPrivate() { }
class Inner {
public void innerPublic() {
outerPrivate(); // 源码层面合法:Inner 可以访问 Outer 的 private 方法
}
}
}
但从 JVM 的角度来看,每个嵌套类最终都会被编译成独立的 .class 文件,而 JVM 的访问控制规则规定:不同类之间不能直接访问彼此的私有成员(Outer 和 Outer$Inner 会被编译成两个独立的 .class 文件,JVM 视它们为两个不同的类)。
为了让代码能够正常运行,Java 编译器(javac)在 Java 11 之前采用了一种"变通方案"------------隐式生成包级可见的桥接方法(如 access$000),由这些桥接方法代为访问私有成员。例如:
scss
// javac 为 Outer 生成的合成桥接方法(示意)
static void access$000(Outer outer) {
outer.outerPrivate(); // 桥接方法调用 private 方法
}
// Inner 中实际调用的是:Outer.access$000(outer)
桥接方法带来三个问题:
- 字节码膨胀:每个被跨类访问的 private 成员都会生成额外的桥接方法;
- 工具干扰:调试器、反射工具和字节码分析工具会看到这些本不应存在的合成方法;
- 反射行为不一致:源码层面合法的 private 访问,通过反射调用时却会抛出 IllegalAccessException。
解决方案:Nest(嵌套)的概念
- Nest:一个顶层类及其内部所有嵌套类型共同组成的访问控制上下文。
- NestHost(嵌套宿主):通常是顶层类,作为 Nest 的标识。
- Nestmate(嵌套伙伴):同一个 Nest 中的所有类型互为 Nestmate。
关键规则:同一个 Nest 中的任意两个 Nestmate,可以直接访问对方的 private 成员,无需桥接方法。
java.lang.Class 类新增了三个 API 来支持嵌套关系的查询:
scss
// 获取当前类的嵌套宿主
Class<?> getNestHost()
// 获取当前嵌套中的所有成员类
Class<?>[] getNestMembers()
// 判断两个类是否属于同一个嵌套
boolean isNestmateOf(Class<?> other)
代码示例:
csharp
public class Outer {
private int outerInt = 10;
private void outerPrivate() {
System.out.println("Outer.outerPrivate() 被调用");
}
class Inner {
public void callOuterPrivate() {
outerPrivate(); // Java 11:直接调用,不再生成桥接方法
System.out.println("outerInt = " + outerInt);
}
}
public static void main(String[] args) throws Exception {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
// 1. 源码层面的 private 访问
inner.callOuterPrivate();
// 2. 反射层面的 private 访问(Java 11 之前会抛 IllegalAccessException)
// Java 11 中正常执行
Method method = Outer.class.getDeclaredMethod("outerPrivate");
method.invoke(outer);
// 3. 查询 Nest 关系
System.out.println("Inner 的 Nest Host: " + Inner.class.getNestHost().getSimpleName());
System.out.println("Outer 的 Nest Members: ");
for (Class<?> c : Outer.class.getNestMembers()) {
System.out.println(" " + c.getName());
}
System.out.println("Inner 是否为 Outer 的 Nestmate: " + Inner.class.isNestmateOf(Outer.class));
}
}
注意事项:
- 仅限同一 Nest:Nest 关系由编译器在编译时确定,仅限于同一顶层类下的嵌套类型。不同顶层类之间即使有继承或包关系,也不构成 Nest;
- 隐式 Nest:如果一个类既没有 NestHost 也没有 NestMembers 属性,则它隐式地以自身为 NestHost,且只有自己一个成员;
2. JFR 开源
Java Flight Recorder(飞行记录器)原本是 Oracle JDK 的商业付费特性,自 Java 11 起正式开源并免费开放,成为强大的生产环境性能诊断工具。
JFR 由两个模块组成:
- jdk.jfr:核心 API 和内部实现,仅依赖 java.base;
- jdk.management.jfr:提供 JMX 接口,依赖 jdk.jfr 和 jdk.management。
JFR 可以通过命令行启动:
ini
# 启动应用并记录60秒的数据到 recording.jfr 文件
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr -jar myapp.jar
# 1. 开始记录
jcmd <pid> JFR.start name=MyRecording duration=60s
# 2. 查看记录状态
jcmd <pid> JFR.check
# 3. 将记录数据导出到文件
jcmd <pid> JFR.dump name=MyRecording filename=recording.jfr
# 4. 停止记录
jcmd <pid> JFR.stop name=MyRecording
3. ZGC 与 Epsilon GC 引入
Java 11 引入了实验性的 ZGC(低延迟垃圾收集器,暂停时间不超过 10ms)和 Epsilon GC(无操作垃圾收集器,仅分配内存不实现任何内存回收机制,用于性能测试),两款全新的垃圾收集器,分别针对超低延迟和零 GC 开销两个极端场景。
(1)ZGC(Z Garbage Collector)核心技术:
- 染色指针(Colored Pointers):利用 64 位指针的高位存储对象状态标记,无需额外的内存开销。
- 读屏障(Load Barrier):在应用线程读取对象引用时,极低成本地检查并修正指针,实现并发重定位。
- 多映射内存(Multi-Mapping):通过虚拟内存映射,让 GC 并发移动对象而不影响应用线程。
ZGC(Z Garbage Collector)启用方式:
ruby
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar myapp.jar
适用场景:对响应延迟极其敏感的大内存应用,如实时交易系统、大型微服务等。
(2)Epsilon GC 是一款"No-Op"(无操作)垃圾收集器。它只负责内存分配,完全不执行任何垃圾回收。当堆内存耗尽时,JVM 直接抛出 OutOfMemoryError 并退出。
Epsilon GC启用方式:
ruby
# 限制堆大小为 100MB,使用 Epsilon GC
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx100m -jar myapp.jar
适用场景:
- 性能基准测试:排除 GC 带来的干扰,纯粹测试应用的内存分配速率和吞吐量。
- 内存压力测试:精确测定应用所需的最低内存阈值。
- 极短生命周期任务:任务执行时间极短,还没来得及产生垃圾就退出了,GC 纯属浪费资源。
4. 安全与加密增强
(1)实现了 TLS 1.3 协议,提供更强的安全性和更快的握手速度,提升了网络通信的安全性。TLS 1.3 引入了新的标准算法名称:
- 协议版本名:TLSv1.3
- 密码套件:TLS_AES_128_GCM_SHA256、TLS_AES_256_GCM_SHA384
(2)引入 ChaCha20 & Poly1305 加密算法以及 Curve25519/Curve448 密钥协商协议。
- ChaCha20 & Poly1305:实现 ChaCha20 流加密算法和 Poly1305 MAC 算法,适用于移动设备等场景;
- Curve25519 / Curve448:支持新的椭圆曲线密钥协商协议,提供更强的安全性。
5. 单文件源代码程序启动
Java 11 允许直接使用 java 命令运行单个 .java 源文件,无需先显式调用 javac 进行编译。在 Java 11 之前,运行一个 Java 程序需要先 javac 编译生成 .class 文件,再 java 运行。
增强 java 启动器,包括在脚本中使用 shebang 文件和相关技术。
bash
# Java 11 之前:需要两步
# javac HelloWorld.java
# java HelloWorld
# Java 11 及之后:一步到位
java HelloWorld.java
Shebang 脚本模式(Linux/macOS):
csharp
#!/usr/bin/java --source 11
public class ScriptDemo {
public static void main(String[] args) {
System.out.println("我是一个 Java 脚本!");
}
}
# 赋予执行权限
chmod +x ScriptDemo.java
# 直接像脚本一样运行
./ScriptDemo.java
# 输出: 我是一个 Java 脚本!
使用限制:
- 所有相关的类必须定义在同一个 .java 文件中,不能依赖外部其他源文件。
- 如果引用了第三方库(非 JDK 自带类),需要通过 -cp 指定 classpath。
- 该特性主要适用于快速测试和小型脚本,企业级项目仍建议使用标准的编译打包流程。
6. 移除与弃用
- 移除了 Java EE 和 CORBA 模块;
- 移除了 Applet API 和 Web Start 支持;
- 移除了 JavaFX;
- 弃用了 Nashorn JavaScript 引擎 和 Pack200 工具。