01.07 Java基础篇|函数式编程与语言新特性总览

01.07 Java基础篇|函数式编程与语言新特性总览

导读

  • 适用人群:希望系统掌握 Java 8+ 函数式风格与 JDK 9-25 新特性的开发者。
  • 目标:理解 Lambda、Stream、Optional 等核心概念,并梳理各版本语言/平台演进,形成迁移与落地指南。
  • 阅读建议:先掌握函数式核心,再按版本时间线了解新特性,最后结合实践案例与面试题复盘。

核心知识架构

函数式编程基础

  • Lambda 表达式(param) -> expression;底层为 invokedynamic + LambdaMetafactory
  • 函数式接口@FunctionalInterface 注解;常见接口 SupplierConsumerFunctionPredicate
  • 方法引用Class::staticMethodinstance::methodClass::new
  • Optional :避免 NullPointerExceptionmapflatMaporElseGet
  • Stream API:惰性求值、无状态/有状态中间操作、终端操作、并行流。

JDK 8 → 25 语言与平台特性按时间线

  • JDK 8(2014) :Lambda、Stream、java.time、默认方法、CompletableFuture
  • JDK 9 :模块化(JPMS)、jshellList.of 不可变集合、Flow 响应式接口。
  • JDK 10 :局部变量类型推断 var;G1 并行 Full GC。
  • JDK 11HttpClient 正式版、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):

  • filtermapflatMappeek
  • 每个元素独立处理,不依赖其他元素
  • 可以并行执行,性能高
java 复制代码
// 无状态操作:每个元素独立处理
users.stream()
    .filter(user -> user.getAge() > 18)  // 无状态
    .map(User::getName)                   // 无状态
    .collect(Collectors.toList());

有状态操作(Stateful):

  • sorteddistinctlimitskip
  • 需要维护状态,依赖其他元素
  • 并行执行时需要额外开销
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 的 bufferwindow 等操作也是有状态的
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 的 collectgroupBy 等操作也有类似的组合能力
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
  • 工具链jdepsjlinkjdeprscan 辅助版本迁移与瘦身。

迁移指南(JDK 8 → 25)

  1. 清点依赖:确认框架与第三方库支持的 LTS 版本;优先升级到 JDK 17/21。
  2. 兼容性评估 :使用 jdeps 识别内部 API 访问;关注 --illegal-access 默认行为变化。
  3. 构建工具升级:Maven 3.8+、Gradle 8+;启用模块化/多发行打包(Multi-Release JAR)。
  4. 语言特性导入策略 :逐步引入 var、Records、Sealed class;搭配 Spotless/Checkstyle 限制滥用。
  5. 虚拟线程实验 :对阻塞型服务开启 Executors.newVirtualThreadPerTaskExecutor(),监控线程栈与资源消耗。
  6. GC 策略优化:基于延迟需求选择 G1/ZGC/Shenandoah;利用 JFR 观察延迟曲线。
  7. 容器化注意事项 :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: 如何迁移到新版本?

  1. 使用 jdeps 分析依赖
  2. 使用 jdeprscan 检查已弃用 API
  3. 逐步迁移,充分测试

延伸阅读

  • 官方 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。
相关推荐
Cricyta Sevina2 小时前
Java IO 基础理论知识笔记
java·开发语言·笔记
MyBFuture2 小时前
C#接口与抽象类:关键区别详解
开发语言·c#·visual studio
晨晖22 小时前
简单排序c语言版
c语言·开发语言
小萌新上大分2 小时前
java线程通信 生产者消费者,synchronized,,ReentrantLock,Condition(笔记备份)
java·多线程·lock·java线程间通信的方式·reentrantlock使用·生产者消费者问题java·java多线程与高并发
それども2 小时前
Spring Bean 的name可以相同吗
java·后端·spring
MediaTea2 小时前
大学 Python 编程基础(合集)
开发语言·python
墨雪不会编程2 小时前
C++ string 详解:STL 字符串容器的使用技巧
java·开发语言·c++
Lucky GGBond2 小时前
实践开发:老系统新增字段我是如何用枚举优雅兼容历史数据的
java
悲喜自渡7212 小时前
Python 编程(gem5 )
java·linux·开发语言