ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java 函数式编程全阶笔记:从小白到专家

Java 函数式编程全阶笔记:从小白到专家 函数式接口作为Java8新特性而言确实新但是Java8已经不新了。本文主要带你从入门到专家全面了解函数式编程。其实主要是自己复习一下学习路径入门篇看懂会写→ 进阶篇熟练避坑→ 专家篇底层原理入门篇从零看懂函数式编程目标理解核心概念能写基础的 Lambda 和 Stream 代码1. 什么是函数式接口1.1 大白话定义函数式接口本质上就是只有一个抽象方法的接口。 它的核心作用是把 “一段代码逻辑” 当成参数传来传去就像传数字、传字符串一样自然。这是 Java 支持 “函数式编程” 的基础。1.2 为什么需要它—— 从冗余到简洁在没有 Lambda 之前我们想给方法传一段逻辑只能用「匿名内部类」写法非常啰嗦java// 旧写法匿名内部类实现 Runnable Runnable task new Runnable() { Override public void run() { System.out.println(执行任务); } }; new Thread(task).start();有了 Lambda 之后因为Runnable就是一个函数式接口只有一个run()方法我们可以直接简写java// 新写法Lambda 表达式 Runnable task () - System.out.println(执行任务); new Thread(task).start();1.3FunctionalInterface注解就像Override用来标记重写方法一样FunctionalInterface用来标记函数式接口。加了这个注解编译器会自动检查接口是不是只有一个抽象方法不符合就直接编译报错不加注解只要符合 “单抽象方法” 规则也算是函数式接口但没有编译期检查2. Lambda 基础语法Lambda 表达式的格式(参数) - { 方法体 }左边参数列表和接口方法的参数对应右边方法体写具体的逻辑常见写法简化规则参数类型可以省略编译器能根据接口推断出来单参数可以省略括号(x) - ...可写成x - ...单语句可以省略大括号和 return方法体只有一行时不用写{}和returnjava// 完整写法 FunctionString, Integer func (String s) - { return s.length(); }; // 简化写法 FunctionString, Integer func s - s.length();3. 四大常用函数式接口必须记住JDK 已经帮我们定义好了最常用的 4 种函数式接口日常开发 90% 的场景都用它们表格接口方法签名作用通俗理解ConsumerTvoid accept(T t)接收 1 个参数没有返回值“消费” 一个值比如打印、修改SupplierTT get()没有参数返回一个值“生产” 一个值比如创建对象FunctionT, RR apply(T t)接收 T返回 R类型转换、数据映射PredicateTboolean test(T t)接收 T返回布尔值条件判断、过滤逐个上手示例java// 1. Consumer消费一个字符串打印它 ConsumerString printer s - System.out.println(s); printer.accept(hello); // 输出 hello // 2. Supplier生产一个随机数 SupplierDouble random () - Math.random(); System.out.println(random.get()); // 输出随机数 // 3. Function把字符串转成它的长度 FunctionString, Integer getLength s - s.length(); System.out.println(getLength.apply(abc)); // 输出 3 // 4. Predicate判断字符串是不是大于3 PredicateString isLong s - s.length() 3; System.out.println(isLong.test(abcd)); // 输出 true4. 方法引用Lambda 的进一步简写当 Lambda 体里只是调用一个已有方法时可以用「方法引用」进一步简化共有 4 种java// 1. 类名::静态方法 FunctionString, Integer f1 Integer::parseInt; // 等同于s - Integer.parseInt(s) // 2. 对象::实例方法 ConsumerString f2 System.out::println; // 等同于s - System.out.println(s) // 3. 类名::实例方法第一个参数当方法调用者 FunctionString, Integer f3 String::length; // 等同于s - s.length() // 4. 类名::new构造器引用 SupplierListString f4 ArrayList::new; // 等同于() - new ArrayList()5. Stream 入门集合操作的 “流水线”5.1 什么是 StreamStream 是用来操作集合 / 数组的流水线工具不用写 for 循环链式调用就能完成过滤、转换、收集等操作。Stream 不是集合不存数据只负责 “计算”就像工厂流水线原料进去经过多道工序最后产出成品5.2 第一个 Stream 例子需求从一个字符串列表里找出长度大于 3 的转成大写收集成新列表。旧写法for 循环javaListString list Arrays.asList(a, abc, abcd, abcde); ListString result new ArrayList(); for (String s : list) { if (s.length() 3) { result.add(s.toUpperCase()); } }Stream 写法javaListString result list.stream() .filter(s - s.length() 3) // 过滤长度3 .map(String::toUpperCase) // 转换转大写 .collect(Collectors.toList());// 收集转成List6. Optional 入门告别空指针Optional是一个 “装值的盒子”用来明确表示 “这个值可能为空”避免到处写if (obj ! null)。常用写法java// 可能为空的值 OptionalString opt Optional.ofNullable(getName()); // 有值就打印没值什么也不做 opt.ifPresent(name - System.out.println(name)); // 没值就返回默认值 String name opt.orElse(默认名字); // 没值就延迟生成默认值推荐节省性能 String name2 opt.orElseGet(() - 默认名字);进阶篇熟练使用与核心规则目标掌握日常开发全场景避开 90% 的常见坑1. Stream 核心特性与操作分类1.1 两大核心特性惰性求值懒加载中间操作只记录 “要做什么”不会立刻执行只有遇到终端操作才会真正开始计算一次性使用一个 Stream 调用过终端操作后就 “用完了”不能再复用否则会抛IllegalStateException1.2 操作分类表格类型特点常见操作中间操作惰性返回新 Stream不触发计算filter、map、flatMap、distinct、sorted、limit、skip、peek终端操作触发计算执行完 Stream 就失效forEach、collect、reduce、count、findFirst、anyMatch1.3 垂直执行模型Stream 不是 “所有元素先过 filter再过 map”而是每个元素依次走完所有中间操作。javaList.of(a, bb, ccc).stream() .filter(s - { System.out.println(过滤: s); return s.length() 1; }) .map(s - { System.out.println(转换: s); return s.toUpperCase(); }) .findFirst();执行顺序过滤:a → 过滤:bb → 转换:bb找到第一个就停止ccc根本不会处理。2. 常用 Stream 操作详解2.1 中间操作java// filter过滤 stream.filter(s - s.startsWith(a)); // map一对一转换 stream.map(String::length); // flatMap一对多扁平化把每个元素拆成多个再合成一个流 // 比如把 [hello,world] 拆成单个字符 stream.flatMap(s - Arrays.stream(s.split())); // distinct去重 stream.distinct(); // sorted排序 stream.sorted(); stream.sorted(Comparator.reverseOrder()); // limit/skip截取、跳过 stream.limit(3); // 取前3个 stream.skip(2); // 跳过前2个 // peek调试用偷看元素不要写业务逻辑 stream.peek(System.out::println);2.2 终端操作java// forEach遍历 stream.forEach(System.out::println); // collect收集成集合 stream.collect(Collectors.toList()); stream.collect(Collectors.toSet()); stream.collect(Collectors.toMap(k - k, v - v.length())); // reduce归约把所有元素合成一个值 // 求和例子 OptionalInteger sum stream.reduce((a, b) - a b); // count统计数量 long count stream.count(); // findFirst/findAny找元素 OptionalString first stream.findFirst(); // anyMatch/allMatch/noneMatch匹配判断 boolean hasLong stream.anyMatch(s - s.length() 5);3. Lambda 变量捕获规则3.1 有效最终Effectively FinalLambda 可以访问外部的局部变量但这个变量必须是有效最终的 —— 也就是初始化之后没有被重新赋值。javaint num 10; Runnable r () - System.out.println(num); // ✅ 正确num 没被改过javaint num 10; num 20; Runnable r () - System.out.println(num); // ❌ 编译报错3.2 为什么要有这个规则Lambda 底层会把变量复制一份到自己内部。如果外部变量能随便改就会出现 “内外值不一致” 的问题引发线程安全和语义混乱。注意只禁止「重新赋值变量」不禁止「修改对象内部状态」javaListString list new ArrayList(); Runnable r () - list.add(ok); // ✅ 合法list 引用没变只是对象内容变了4. 高频踩坑避坑指南4.1peek()绝对不要写业务逻辑peek设计初衷只是调试打印当终端操作是count()且中间不改变元素数量时Stream 库会直接跳过遍历peek完全不执行并行流中peek执行顺序不确定4.2 Stream 不能复用javaStreamString stream list.stream(); stream.count(); stream.forEach(System.out::println); // ❌ 抛出 IllegalStateException4.3 非干扰原则Stream 执行过程中不能修改底层数据源否则可能出现并发修改异常或不确定结果。javaListString list new ArrayList(List.of(a,b)); list.stream().forEach(s - list.add(c)); // ❌ 危险操作4.4orElse和orElseGet的区别orElse(默认值)不管 Optional 有没有值都会创建默认值对象orElseGet(Supplier)只有没值的时候才会执行创建逻辑当默认值创建成本高时永远用orElseGet4.5 Optional 禁忌❌ 不要用 Optional 当方法参数、类字段❌ 不要写if (opt.isPresent()) { opt.get() }这和判空没区别✅ 只用来当方法返回值明确告诉调用方 “可能为空”5. try-with-resources 进阶5.1 基础用法用来自动关闭资源流、连接等不用手写finally资源必须实现AutoCloseable接口。java// Java 7 try (InputStream in new FileInputStream(a.txt)) { // 读文件 } // 自动关闭 injava// Java 9 增强变量已经是有效最终可以直接放进去 InputStream in new FileInputStream(a.txt); try (in) { // 读文件 }5.2 异常抑制如果try块抛了异常close()也抛了异常主异常是业务异常close 的异常会被抑制可以通过e.getSuppressed()获取。注意只有 try 块也抛异常时才会抑制如果 try 块正常结束close 抛的异常会正常向上抛。专家篇底层原理与工程高阶目标理解 JVM 底层实现掌握性能调优与工程规范1. 函数式接口 JLS 规范与编译期机制1.1 SAM 判定严格规则函数式接口的 “单抽象方法” 计数遵循以下排除规则与java.lang.Object的 public 方法签名一致的抽象方法不计入如equals(Object)default方法、static方法不计入协变返回类型的重写方法视为同一方法1.2 编译期三重屏障编译器在 Lambda 与接口匹配时会做三层严格校验签名协变匹配参数类型兼容返回值支持协变如返回String可赋值给CharSequence异常契约兼容Lambda 抛出的受检异常必须是接口throws声明的子集重载消歧重载方法中编译器通过目标类型反向推导最匹配的版本推导失败需显式转型或类型见证java// 重载歧义示例 void execute(Runnable r) {} void execute(Callable? c) {} // execute(() - {}); // 编译错误歧义 execute((Runnable) () - {}); // 显式转型解决2. Lambda 运行时底层原理2.1 invokedynamic LambdaMetafactoryLambda 不是匿名内部类的语法糖底层基于 JDK 7 引入的invokedynamic指令编译时生成一个invokedynamic调用点不生成新类运行时第一次执行时由LambdaMetafactory动态生成接口实现类、创建实例并绑定调用点后续执行直接复用已链接的调用点2.2 捕获型 vs 非捕获型非捕获型 Lambda不引用外部变量JVM 可以缓存复用实例但不保证单例不同调用点可能生成不同类捕获型 Lambda引用了外部变量每次调用通常创建新实例捕获的变量会作为字段存入生成的对象2.3 JIT 优化边界严格区分阶段以下优化发生在 JIT 编译期不是LambdaMetafactory链接阶段且不保证必然触发常量传播捕获的是编译期常量时JIT 会将常量内联把捕获型退化为非捕获型实现复用逃逸分析判定 Lambda 实例不逃逸时会直接消除对象分配把方法体内联调用⚠️ 工程警示绝对不能依赖 Lambda 实例的做业务逻辑这是厂商实现细节跨版本 / 跨虚拟机行为不一致。3. Stream 底层执行机制3.1 Spliterator 数据源抽象Stream 底层基于Spliterator可分割迭代器作为数据源抽象核心能力tryAdvance逐个处理元素实现拉模式惰性迭代trySplit拆分数据源为并行流提供基础特征标记SIZED已知大小、SUBSIZED子拆分也已知大小、ORDERED有序等驱动库层优化3.2 管道构建与执行中间操作只构建双向链表形式的操作管道每个阶段只记录下游引用终端操作触发时从数据源开始以tryAdvance拉模式逐个推送元素穿过管道短路操作通过tryAdvance返回false终止迭代实现提前退出3.3count()优化原理当源流具备SIZED特征且中间操作不改变元素数量时count()会直接返回源流大小不触发任何元素遍历这也是peek不执行的根本原因库层优化非 JIT。4. 并行流深度解析4.1 底层架构默认使用ForkJoinPool.commonPool()默认并行度 CPU 核心数 - 1基于 Fork/Join 框架递归拆分任务 → 多线程执行 → 结果合并4.2 拆分效率差异表格数据源拆分效率原因ArrayList、数组、IntStream.range高Spliterator 具备SUBSIZED特征可均匀对半拆分LinkedList、Stream.iterate低无法预估元素数量拆分粒度不可控并行收益极低4.3 并行流性能陷阱有状态全量操作distinct()、sorted()在并行流中需要全量缓冲和线程同步通常比串行更慢小数据量线程调度开销大于并行收益共享可变状态会产生数据竞争结果错误IO 阻塞任务会耗尽全局 commonPool拖垮整个应用4.4 生产级最佳实践自定义线程池隔离阻塞任务javaForkJoinPool customPool new ForkJoinPool(4); customPool.submit(() - list.parallelStream().map(...).collect(Collectors.toList()) ).get(); customPool.shutdown();规范说明该用法依赖 HotSpot 实现细节非 Java 语言规范强制保证但目前是工业界隔离 commonPool 的标准方案。5. 高级契约与序列化5.1 Lambda 序列化机制Lambda 本身不天然支持序列化必须满足目标函数式接口继承Serializable捕获的所有变量都可序列化 底层会生成SerializedLambda元数据进行传输反序列化时通过LambdaMetafactory重建实例而非传输字节码。5.2 目标类型与类型推断Lambda 本身没有独立类型完全由上下文的「目标类型」决定。同一个 Lambda 在不同上下文可对应不同接口类型编译器通过目标类型反向推导参数、返回值类型。6. 版本演进与性能优化表格版本核心增强底层价值Java 8Lambda、Stream、Optional 正式发布函数式编程基石Java 9Stream.iterate 有限重载、Optional 增强有限流并行性能大幅提升Java 10Optional.orElseThrow () 无参重载语义更简洁Java 11Lambda 形参支持 var统一类型推断语法Java 16Stream.mapMulti替代 flatMap 高频场景减少 Stream 对象开销Java 21虚拟线程结合函数式接口大幅提升 IO 密集型并发吞吐量7. 一句话核心定论函数式接口 SAM 契约 编译期三重校验 invokedynamic 运行时绑定 有效最终值捕获Stream Spliterator 数据源抽象 惰性中间管道 垂直短路执行 非干扰原则现代 Java 函数式体系 行为解耦能力 集合流式抽象 空安全与异常机制三者共同构成了 Java 面向现代开发的核心编程范式。文章收录专栏Java 核心原理全解源码・并发・面试实战
返回列表