函数式编程学习(Java)
·
一、函数式接口
函数式接口是 Lambda 表达式和方法引用的容器,是整个 Java 函数式编程的基础,所有 Lambda 最终都要绑定到某个函数式接口的抽象方法上。
1. 核心定义
仅包含 一个抽象方法 的接口(可包含多个默认方法、静态方法、私有方法),推荐用 @FunctionalInterface 注解标记(编译器会强制校验是否符合规范)。
2. 核心价值
将「行为(方法逻辑)」抽象为接口类型,让行为可以像变量一样被传递、赋值、作为方法参数/返回值。
3. 常用内置函数式接口(java.util.function 包)
| 接口类型 | 核心方法 | 入参/返回值 | 典型用途 |
|---|---|---|---|
| 消费型 Consumer | void accept(T t) | 入参 T,无返回 | 遍历集合、打印数据 |
| 供给型 Supplier | T get() | 无入参,返回 T | 生成随机数、创建对象 |
| 函数型 Function<T,R> | R apply(T t) | 入参 T,返回 R | 数据转换(如字符串转数字) |
| 断言型 Predicate | boolean test(T t) | 入参 T,返回布尔 | 过滤集合、条件判断 |
| 二元函数 BiFunction<T,U,R> | R apply(T t, U u) | 入参 T+U,返回 R | 双参数转换(如两数运算) |
4. 基础示例(自定义+使用)
// 自定义函数式接口(单一抽象方法)
@FunctionalInterface
interface MyFunction {
String process(String s); // 唯一抽象方法
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// 给函数式接口赋值(后续用Lambda实现)
MyFunction upperCase = s -> s.toUpperCase();
MyFunction addSuffix = s -> s + "_suffix";
// 调用抽象方法执行逻辑
System.out.println(upperCase.process("hello")); // HELLO
System.out.println(addSuffix.process("test")); // test_suffix
}
}
二、Lambda 表达式
Lambda 是函数式接口的简洁实例化方式,替代冗长的匿名内部类,本质是「一段可传递的代码块」。
1. 核心语法
(参数列表) -> { 方法体 }
简化规则(核心!减少冗余)
| 场景 | 简化写法 | 原始写法 |
|---|---|---|
| 单参数 | s -> s.toUpperCase() |
(String s) -> { return s.toUpperCase(); } |
| 无参数 | () -> Math.random() |
() -> { return Math.random(); } |
| 单语句方法体 | (a, b) -> a + b |
(int a, int b) -> { return a + b; } |
| 多语句方法体 | (a, b) -> { int c = a*2; return c + b; } |
必须带大括号和return |
2. 使用场景:绑定函数式接口
Lambda 不能单独存在,必须绑定到「函数式接口的抽象方法」,常见场景:
场景1:赋值给函数式接口变量
// 绑定Consumer接口
Consumer<String> print = s -> System.out.println(s);
print.accept("Lambda赋值示例"); // 输出:Lambda赋值示例
// 绑定Predicate接口
Predicate<Integer> isOdd = num -> num % 2 != 0;
System.out.println(isOdd.test(5)); // 输出:true
场景2:作为方法参数(行为参数化)
// 自定义方法:接收函数式接口参数
public static <T> List<T> filter(List<T> list, Predicate<T> condition) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (condition.test(t)) {
result.add(t);
}
}
return result;
}
// 调用:传入Lambda作为过滤条件
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1,2,3,4,5);
// 过滤偶数
List<Integer> evens = filter(nums, num -> num % 2 == 0);
System.out.println(evens); // 输出:[2,4]
}
3. 关键注意点
- Lambda 可捕获外部变量,但变量必须是
final或「有效 final」(未显式声明 final,但从未重新赋值); - Lambda 没有自己的
this,this指向外部类的对象; - 编译期会将 Lambda 转换为函数式接口的匿名实现类(运行期优化为 invokedynamic 指令)。
三、方法引用
方法引用是 Lambda 的简化写法,当 Lambda 体仅调用一个已有方法时,可用「方法引用」替代,让代码更简洁。
1. 语法格式
| 方法引用类型 | 语法 | 等效 Lambda 示例 |
|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Integer::sum → (a,b) -> Integer.sum(a,b) |
| 实例方法引用(对象) | 对象名::实例方法名 | str::toUpperCase → () -> str.toUpperCase() |
| 实例方法引用(类) | 类名::实例方法名 | String::length → s -> s.length() |
| 构造器引用 | 类名::new | ArrayList::new → () -> new ArrayList<>() |
2. 实战示例
public class MethodReferenceDemo {
public static void main(String[] args) {
// 1. 静态方法引用:Integer::sum
BinaryOperator<Integer> sum = Integer::sum;
System.out.println(sum.apply(10, 20)); // 30
// 2. 实例方法引用(对象):自定义字符串对象的toUpperCase
String str = "hello";
Supplier<String> upperCase = str::toUpperCase;
System.out.println(upperCase.get()); // HELLO
// 3. 实例方法引用(类):String::length
Function<String, Integer> strLength = String::length;
System.out.println(strLength.apply("test")); // 4
// 4. 构造器引用:ArrayList::new
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
list.add("a");
System.out.println(list); // [a]
}
}
3. 使用技巧
- 方法引用的参数列表、返回值必须和绑定的函数式接口抽象方法完全匹配;
- 优先使用方法引用替代简单 Lambda,提升代码可读性(如 Stream 操作中)。
四、Stream 流(函数式集合操作)
Stream 是 Java 提供的「函数式集合处理工具」,基于 Lambda 和函数式接口实现,支持「管道式」数据处理,替代传统的 for 循环,核心是「声明式编程」(关注做什么,而非怎么做)。
1. Stream 核心特性
- 不存储数据:仅处理数据源(集合、数组等),不修改原数据;
- 惰性求值:中间操作(如 filter、map)仅记录逻辑,终端操作(如 forEach、collect)才触发计算;
- 可并行:通过
parallelStream()实现并行处理,简化多线程编程; - 一次性:流只能被消费一次,再次使用需重新创建。
2. 核心操作分类
| 操作类型 | 常用方法 | 特点 |
|---|---|---|
| 中间操作 | filter、map、distinct、sorted、limit | 返回 Stream,可链式调用,惰性执行 |
| 终端操作 | forEach、collect、reduce、count、anyMatch | 返回非 Stream 结果,触发计算 |
3. 实战示例(核心场景)
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamDemo {
// 定义测试实体类
static class User {
private String name;
private int age;
private String city;
public User(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
// 省略getter/setter
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
}
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", 22, "Beijing"),
new User("Bob", 28, "Shanghai"),
new User("Charlie", 22, "Beijing"),
new User("David", 30, "Shanghai")
);
// 场景1:过滤+映射 → 获取北京22岁用户的姓名
List<String> names = users.stream()
.filter(u -> u.getAge() == 22 && "Beijing".equals(u.getCity())) // 中间操作
.map(User::getName) // 方法引用简化Lambda
.collect(Collectors.toList()); // 终端操作
System.out.println(names); // [Alice, Charlie]
// 场景2:分组 → 按城市分组用户
Map<String, List<User>> groupByCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
System.out.println(groupByCity); // {Beijing=[Alice, Charlie], Shanghai=[Bob, David]}
// 场景3:归约 → 计算所有用户的年龄总和
int totalAge = users.stream()
.mapToInt(User::getAge) // 避免装箱,提升性能
.sum(); // 终端操作
System.out.println(totalAge); // 102
// 场景4:并行流 → 大数据量处理(自动多线程)
long count = users.parallelStream()
.filter(u -> u.getAge() > 25)
.count();
System.out.println(count); // 2
}
}
4. 高频技巧
- 基本类型流(
IntStream/LongStream/DoubleStream):避免自动装箱/拆箱,提升性能; Optional结合 Stream:处理可能为空的结果(如findFirst()返回Optional<User>);collect()是最灵活的终端操作:支持转集合、分组、拼接字符串(Collectors.joining())等。
五、核心总结
- 函数式接口是基础:定义了「行为的类型」,是 Lambda 和方法引用的「载体」;
- Lambda 表达式是函数式接口的「简洁实现」:替代匿名内部类,直接表达行为逻辑;
- 方法引用 简化Lambda :进一步简化仅调用单个方法的 Lambda;
- Stream 流是函数式接口+Lambda 的「实战场景」:通过接收函数式接口参数(如 filter 接收 Predicate),实现声明式集合处理。
六、避坑
- Stream 不要滥用:小数据量场景下,传统 for 循环可能比 Stream 更快(无线程开销);
- Lambda 不要写太复杂:复杂逻辑拆分为方法,用方法引用替代,提升可读性;
- 并行流注意线程安全:数据源若为线程不安全的集合(如 ArrayList),并行流处理时需注意;
- 函数式接口必须满足单一抽象方法:用
@FunctionalInterface注解强制校验,避免后续误加抽象方法。
实战练习
需求:从员工列表中筛选出「部门为研发、年龄>30岁」的员工,按薪资降序排序,最终获取他们的姓名和薪资(格式:姓名-薪资),拼接成字符串。
// 自行实现以下逻辑,检验掌握程度
List<Employee> employees = // 初始化员工列表
String result = employees.stream()
.filter(/* 研发部门+年龄>30 */)
.sorted(/* 薪资降序 */)
.map(/* 转换为 姓名-薪资 字符串 */)
.collect(/* 拼接为逗号分隔的字符串 */);
(参考答案:
String result = employees.stream()
.filter(e -> "研发".equals(e.getDept()) && e.getAge() > 30)
.sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()))
.map(e -> e.getName() + "-" + e.getSalary())
.collect(Collectors.joining(","));
)
更多推荐


所有评论(0)