01.07 Java基础篇|函数式编程与语言新特性总览
导读
- 适用人群:希望系统掌握 Java 8+ 函数式风格与 JDK 9-25 新特性的开发者。
- 目标:理解 Lambda、Stream、Optional 等核心概念,并梳理各版本语言/平台演进,形成迁移与落地指南。
- 阅读建议:先掌握函数式核心,再按版本时间线了解新特性,最后结合实践案例与面试题复盘。
核心知识架构
函数式编程基础
- Lambda 表达式 :
(param) -> expression;底层为invokedynamic+LambdaMetafactory。 - 函数式接口 :
@FunctionalInterface注解;常见接口Supplier、Consumer、Function、Predicate。 - 方法引用 :
Class::staticMethod、instance::method、Class::new。 - Optional :避免
NullPointerException;map、flatMap、orElseGet。 - Stream API:惰性求值、无状态/有状态中间操作、终端操作、并行流。
JDK 8 → 25 语言与平台特性按时间线
- JDK 8(2014) :Lambda、Stream、
java.time、默认方法、CompletableFuture。 - JDK 9 :模块化(JPMS)、
jshell、List.of不可变集合、Flow响应式接口。 - JDK 10 :局部变量类型推断
var;G1 并行 Full GC。 - JDK 11 :
HttpClient正式版、var支持 Lambda 参数、ZGC 实验特性。 - JDK 12-13 :Switch 表达式预览、
Text Blocks提议、Shenandoah GC。 - JDK 14-17 :记录类型(Record)、密封类(Sealed)、模式匹配(Pattern Matching)、
instanceof改进、JEP 409/406。 - JDK 18-21:虚拟线程(Project Loom)、结构化并发、Vector API、Pattern Matching for Switch 正式版。
- JDK 22-25(计划与预览):记录模式(Record Patterns)、字符串模板(String Templates)、无穷期增强的虚拟线程调度器、内存 API(Project Panama)、K2 编译器雏形。
平台层增强
- 垃圾收集器演进:G1 → ZGC → Shenandoah;理解停顿时间与吞吐的取舍。
- 类加载与隐式类:JEP 445(未命名类与实例主方法)。
- 本地互操作 :
Foreign Function & Memory API(JEP 442、454、457)。 - Project Valhalla 展望:原生值类型、泛型特化(预计 JDK 25+)。
源码讲解|示例拆解
示例 1:Stream Pipeline 深度解析
基础示例:
java
List<String> emails = users.stream()
.filter(user -> user.isActive() && user.getLastLogin().isAfter(Instant.now().minus(30, ChronoUnit.DAYS)))
.sorted(Comparator.comparing(User::getCreatedAt))
.map(User::getEmail)
.limit(100)
.collect(Collectors.toList());
面试亮点深度解析:
1. 惰性求值(Lazy Evaluation)
核心概念:
- Stream 的中间操作是惰性的,不会立即执行
- 只有终端操作才会触发整个 Pipeline 的执行
- 这允许 Stream 进行优化,如短路操作、合并操作
执行流程:
java
// 步骤1:创建 Stream(不执行任何操作)
Stream<User> stream = users.stream();
// 步骤2:添加中间操作(不执行,只记录操作)
Stream<User> filtered = stream.filter(user -> user.isActive());
Stream<User> sorted = filtered.sorted(Comparator.comparing(User::getCreatedAt));
Stream<String> mapped = sorted.map(User::getEmail);
Stream<String> limited = mapped.limit(100);
// 步骤3:终端操作(触发整个 Pipeline 执行)
List<String> result = limited.collect(Collectors.toList());
优化示例:
java
// 由于惰性求值,以下代码不会处理所有元素
Optional<String> first = users.stream()
.filter(user -> user.isActive())
.map(User::getEmail)
.findFirst(); // 找到第一个就停止,不会处理后续元素
// 等价于(但 Stream 更高效)
String firstEmail = null;
for (User user : users) {
if (user.isActive()) {
firstEmail = user.getEmail();
break; // 找到就停止
}
}
与响应式编程的关联:
- Stream 的惰性求值类似于 Reactive Streams 的延迟订阅
- 在 [02.04 Java并发编程|Stream、响应式与 Netty 全景对比]中会看到 Reactor 的 Flux/Mono 也有类似的惰性特性
2. 无状态 vs 有状态中间操作
无状态操作(Stateless):
filter、map、flatMap、peek- 每个元素独立处理,不依赖其他元素
- 可以并行执行,性能高
java
// 无状态操作:每个元素独立处理
users.stream()
.filter(user -> user.getAge() > 18) // 无状态
.map(User::getName) // 无状态
.collect(Collectors.toList());
有状态操作(Stateful):
sorted、distinct、limit、skip- 需要维护状态,依赖其他元素
- 并行执行时需要额外开销
java
// 有状态操作:需要维护状态
users.stream()
.sorted(Comparator.comparing(User::getAge)) // 有状态:需要排序
.distinct() // 有状态:需要去重
.limit(10) // 有状态:需要计数
.collect(Collectors.toList());
性能影响:
java
// 优化:将有状态操作放在最后
List<String> result = users.stream()
.filter(user -> user.isActive()) // 无状态:先过滤,减少数据量
.map(User::getEmail) // 无状态
.distinct() // 有状态:在数据量减少后执行
.sorted() // 有状态:在数据量减少后执行
.collect(Collectors.toList());
与响应式编程的关联:
- 在 [02.04 Java并发编程|Stream、响应式与 Netty 全景对比]中会看到 Reactor 的
buffer、window等操作也是有状态的
3. Collectors 的可组合性
基础用法:
java
// 简单收集
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList());
// 分组收集
Map<String, List<User>> byCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
// 统计收集
IntSummaryStatistics stats = users.stream()
.collect(Collectors.summarizingInt(User::getAge));
组合使用:
java
// 多级分组
Map<String, Map<String, List<User>>> byCityAndAge = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.groupingBy(user -> user.getAge() > 18 ? "Adult" : "Minor")
));
// 分组 + 统计
Map<String, Double> avgAgeByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.averagingInt(User::getAge)
));
// 分组 + 收集
Map<String, Set<String>> emailsByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.mapping(User::getEmail, Collectors.toSet())
));
自定义 Collector:
java
// 自定义 Collector:收集为逗号分隔的字符串
Collector<User, StringBuilder, String> commaSeparated = Collector.of(
StringBuilder::new, // Supplier:创建累加器
(sb, user) -> { // Accumulator:累加元素
if (sb.length() > 0) sb.append(", ");
sb.append(user.getName());
},
(sb1, sb2) -> { // Combiner:合并(并行流使用)
if (sb1.length() > 0 && sb2.length() > 0) {
sb1.append(", ");
}
return sb1.append(sb2);
},
StringBuilder::toString // Finisher:最终转换
);
String result = users.stream()
.collect(commaSeparated);
与响应式编程的关联:
- 在[02.04 Java并发编程|Stream、响应式与 Netty 全景对比]中会看到 Reactor 的
collect、groupBy等操作也有类似的组合能力
4. Stream Pipeline 的底层实现
Pipeline 结构:
java
// Stream Pipeline 由多个 Stage 组成
abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
implements PipelineHelper<E_OUT>, Spliterator<E_OUT> {
// 上游 Stage
private AbstractPipeline sourceStage;
// 下游 Stage
private AbstractPipeline nextStage;
// 数据源
private Spliterator<?> sourceSpliterator;
}
执行流程:
java
// 1. 创建 Stream(不执行)
Stream<User> stream = users.stream();
// 2. 添加中间操作(创建新的 Stage)
Stream<User> filtered = stream.filter(predicate); // 创建 FilterOp
Stream<String> mapped = filtered.map(mapper); // 创建 MapOp
// 3. 终端操作(触发执行)
List<String> result = mapped.collect(collector);
// 内部执行:
// - 从数据源获取 Spliterator
// - 通过 Pipeline 传递数据
// - 应用每个中间操作
// - 使用 Collector 收集结果
并行流执行:
java
// 并行流使用 ForkJoinPool
List<String> result = users.parallelStream()
.filter(user -> user.isActive())
.map(User::getEmail)
.collect(Collectors.toList());
// 内部执行:
// 1. 使用 Spliterator 分割数据
// 2. 提交到 ForkJoinPool
// 3. 并行处理各个分片
// 4. 合并结果
与响应式编程的对比:
- Stream:拉模式(Pull),消费者主动拉取数据
- Reactor:推模式(Push),发布者主动推送数据
- 在[02.04 Java并发编程|Stream、响应式与 Netty 全景对比]中会详细对比这两种模式
示例 2:记录类型 + 模式匹配
java
public record OrderEvent(String orderId, long timestamp, EventType type) {}
static String describe(Object obj) {
return switch (obj) {
case OrderEvent(var id, long ts, EventType type) -> "[%s] at %d -> %s".formatted(id, ts, type);
case String s when s.isBlank() -> "Empty string";
case String s -> "String: " + s;
case null -> "Null";
default -> "Unknown";
};
}
面试亮点:展示记录类型自动生成构造器/
equals/hashCode,以及 switch 模式匹配、守卫(when)。
示例 3:虚拟线程与结构化并发
java
try (var scope = StructuredTaskScope.ShutdownOnFailure.builder().build()) {
Future<User> userFuture = scope.fork(() -> userService.fetch(userId));
Future<List<Order>> ordersFuture = scope.fork(() -> orderService.listRecent(userId));
scope.join();
scope.throwIfFailed();
return new UserProfile(userFuture.resultNow(), ordersFuture.resultNow());
}
面试亮点:解释虚拟线程对阻塞调用的优化、结构化并发的取消/异常管理模型。
实战伪代码
场景:构建响应式审批流
function processApproval(request):
return Mono.just(request)
.switchIfEmpty(error("非法请求"))
.flatMap(validate)
.zipWith(fetchRiskScore(request.userId))
.flatMap(tuple -> {
result = tuple.apply(decide)
if result.approved:
return publishEvent(request, result)
else:
return notifyRejected(request, result.reason)
})
.onErrorResume(ex -> {
log.error("审批失败", ex)
return Mono.just(ApprovalResult.retry(request.id))
})
关键点:函数式链式处理、错误恢复、结合 Reactor/CompletableFuture 的落地方式。
应用场景
- 批量数据处理:使用 Stream/Parallel Stream + Collector 聚合。
- 响应式系统:Project Reactor、RxJava、Flow API;减轻阻塞。
- 并发优化:虚拟线程处理大量 IO 绑定任务;结构化并发管理依赖关系。
- 语言升级迁移 :引入 Records 精简 DTO、Sealed class 限制继承、防御式
switch。 - 工具链 :
jdeps、jlink、jdeprscan辅助版本迁移与瘦身。
迁移指南(JDK 8 → 25)
- 清点依赖:确认框架与第三方库支持的 LTS 版本;优先升级到 JDK 17/21。
- 兼容性评估 :使用
jdeps识别内部 API 访问;关注--illegal-access默认行为变化。 - 构建工具升级:Maven 3.8+、Gradle 8+;启用模块化/多发行打包(Multi-Release JAR)。
- 语言特性导入策略 :逐步引入
var、Records、Sealed class;搭配 Spotless/Checkstyle 限制滥用。 - 虚拟线程实验 :对阻塞型服务开启
Executors.newVirtualThreadPerTaskExecutor(),监控线程栈与资源消耗。 - GC 策略优化:基于延迟需求选择 G1/ZGC/Shenandoah;利用 JFR 观察延迟曲线。
- 容器化注意事项 :JDK 10+ 默认感知 cgroup;合理配置
-XX:MaxRAMPercentage。
高频面试问答(深度解析)
1. Lambda 底层如何实现?
标准答案:
- 编译器生成
invokedynamic指令 - 运行时使用
LambdaMetafactory创建函数式接口实例 - 避免生成额外的
.class文件
深入追问与回答思路:
Q: invokedynamic 的优势?
java
// Lambda 表达式
Function<String, Integer> func = s -> s.length();
// 编译后的字节码(简化)
invokedynamic #0:apply()Ljava/util/function/Function;
// 运行时动态生成实现类
// 第一次调用时生成,后续复用
Q: Lambda 的性能?
- 首次调用:需要生成实现类,稍慢
- 后续调用:与普通方法调用性能相同
- 优化:JVM 会内联 Lambda 表达式
2. Optional 的最佳实践?
标准答案:
- 用于返回值:包装可能为 null 的返回值
- 避免作为字段:不要用 Optional 作为类字段
- 避免作为参数:不要用 Optional 作为方法参数
深入追问与回答思路:
Q: 为什么 Optional 不能作为字段?
java
// 不推荐
public class User {
private Optional<String> email; // 不推荐
}
// 推荐
public class User {
private String email; // 可能为 null
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
}
Q: Optional 的正确使用?
java
// 1. 返回值包装
public Optional<User> findById(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}
// 2. 链式调用
user.flatMap(User::getEmail)
.map(String::toUpperCase)
.orElse("unknown");
// 3. 条件执行
user.ifPresentOrElse(
u -> System.out.println("Found: " + u),
() -> System.out.println("Not found")
);
3. var 的限制?
标准答案:
- 仅用于局部变量
- 必须初始化
- 不能用于 Lambda 参数(JDK 11+ 支持)
- 不能用于方法参数和返回类型
深入追问与回答思路:
Q: var 的类型推断规则?
java
// 推断为 String
var name = "Alice";
// 推断为 List<String>
var list = new ArrayList<String>();
// 不能推断(编译错误)
var x; // 必须初始化
var y = null; // 不能推断 null 的类型
Q: 什么时候用 var?
- 复杂类型 :
var map = new HashMap<String, List<Integer>>(); - 提高可读性:减少重复的类型声明
- 避免使用 :简单类型(如
int i = 0;)
4. Record 与 Lombok @Data 的区别?
标准答案:
| 特性 | Record | Lombok @Data |
|---|---|---|
| 类型 | 语言特性 | 编译时生成 |
| 不可变性 | 是 | 否(可修改) |
| 继承 | 不能继承类 | 可以 |
| 解构 | 支持 | 不支持 |
深入追问与回答思路:
Q: Record 的使用场景?
java
// DTO、值对象
public record UserDTO(String id, String name, int age) {
// 自动生成:
// - final 字段
// - 构造器
// - equals/hashCode/toString
// - getter 方法
}
// 解构模式(JDK 21+)
if (obj instanceof UserDTO(var id, var name, var age)) {
System.out.println(id + ": " + name);
}
5. 虚拟线程与线程池的关系?
标准答案:
- 虚拟线程运行在平台线程(ForkJoinPool)上
- 每个任务一个虚拟线程
- 阻塞时自动 yield,释放平台线程
- 适合大量短阻塞任务
深入追问与回答思路:
Q: 虚拟线程的创建?
java
// 方式1:Thread.ofVirtual()
Thread virtualThread = Thread.ofVirtual()
.name("worker-", 0)
.start(() -> {
// 任务
});
// 方式2:ExecutorService
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 任务
});
}
Q: 虚拟线程的适用场景?
- IO 密集型:网络请求、数据库查询
- 大量并发:可以创建百万级虚拟线程
- 不适合:CPU 密集型任务
6. JDK 17/21/25 LTS 的关键变化?
标准答案:
JDK 17:
- Record、Sealed Class、Pattern Matching 正式版
- 强封装:默认不允许反射访问内部 API
JDK 21:
- 虚拟线程 GA
- 结构化并发预览
- 字符串模板预览
JDK 25(最新 LTS):
- 字符串模板正式版
- 记录模式增强
- 性能优化
深入追问与回答思路:
Q: 如何迁移到新版本?
- 使用
jdeps分析依赖 - 使用
jdeprscan检查已弃用 API - 逐步迁移,充分测试
延伸阅读
- 官方 JEP 索引:https://openjdk.org/jeps/0
- Project Loom、Panama、Valhalla 官方博客
- 《Modern Java in Action》《Programming with Java Lambdas》
- GitHub:
awesome-java,java-new-features汇总项目 - 推荐实践:搭建示例仓库展示 JDK 8/11/17/21/25 代码差异,附迁移脚本与 CI Matrix。