10Java Lambda 表达式与函数式编程


✨博客主页: https://blog.csdn.net/m0_63815035?type=blog
💗《博客内容》:大数据、AI开发、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识
📢博客专栏: https://blog.csdn.net/m0_63815035/category_11954877.html
📢欢迎点赞 👍 收藏 ⭐留言 📝
📢本文为学习笔记资料,如有侵权,请联系我删除,疏漏之处还请指正🙉
📢大厦之成,非一木之材也;大海之阔,非一流之归也✨

目录
Lambda 表达式是 Java 8 引入的最重要的特性之一,它让 Java 开始支持函数式编程风格。本文从基础到高阶,全面介绍 Lambda 表达式的语法、函数式接口、方法引用、构造器引用、变量捕获以及与 Stream API 的结合,帮助你写出更简洁、更灵活的代码。
一、为什么需要 Lambda

1.1 传统方式的问题
在 Java 8 之前,我们经常需要传递“行为”——比如点击按钮时的回调、集合排序的比较器、多线程的 Runnable。这些行为通常通过匿名内部类实现,代码冗长,可读性差。
// 匿名内部类示例
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行");
}
}).start();
这种方式的问题:
- 语法重复(
new Runnable,@Override,方法签名) - 代码不够直观,核心逻辑(
System.out.println)被大量样板代码包围 - 变量捕获规则复杂(必须
final或 effectively final)
1.2 Lambda 的解决方案
Lambda 表达式将焦点放在参数和方法体上,让代码更像一个函数:
new Thread(() -> System.out.println("线程执行")).start();
Lambda 表达式可以看作是匿名方法——没有名字,只有参数列表、箭头和方法体。
二、Lambda 表达式基础语法
2.1 完整语法格式
(参数类型 参数名1, 参数类型 参数名2, ...) -> { 方法体 }
箭头左侧是参数列表,右侧是执行代码。
2.2 简化规则
| 情形 | 可省略的内容 | 示例 |
|---|---|---|
| 参数类型可以推断 | 参数类型 | (a, b) -> a + b |
| 只有一个参数 | 小括号 | a -> a * a |
| 方法体只有一条语句 | 大括号和 return(如果有返回值) |
() -> 42 |
注意:参数类型推断是基于函数式接口抽象方法的参数类型,编译器可以自动推导,无需显式写出。
2.3 无参数示例
// 无参,无返回值
Runnable r = () -> System.out.println("hello");
// 无参,有返回值
Supplier<Integer> supplier = () -> 100;
2.4 单参数示例
// 带参数,无返回值
Consumer<String> printer = s -> System.out.println(s);
// 带参数,有返回值
Function<Integer, String> converter = i -> String.valueOf(i);
2.5 多参数示例
// 两个参数,有返回值
BinaryOperator<Integer> add = (a, b) -> a + b;
// 两参数,方法体多条语句,需要大括号和 return
Comparator<Integer> compare = (a, b) -> {
System.out.println("比较中...");
return a - b;
};
三、函数式接口(Functional Interface)
3.1 定义
函数式接口是只有一个抽象方法的接口。可以有多个默认方法或静态方法,但抽象方法只能有一个。
@FunctionalInterface
interface MyFunc {
void doSomething();
// void another(); // 报错,只能有一个抽象方法
}
@FunctionalInterface 注解不是必需的,但加上后编译器会检查,推荐使用。
3.2 Java 8 内置四大核心函数式接口
| 接口 | 抽象方法 | 描述 |
|---|---|---|
Consumer<T> |
void accept(T t) |
消费一个数据 |
Supplier<T> |
T get() |
提供一个数据 |
Function<T,R> |
R apply(T t) |
输入 T,输出 R |
Predicate<T> |
boolean test(T t) |
判断一个条件 |
使用示例
// Consumer:打印
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello");
// Supplier:生成随机数
Supplier<Double> random = () -> Math.random();
System.out.println(random.get());
// Function:字符串长度
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Lambda"));
// Predicate:判断字符串是否为空
Predicate<String> isEmpty = s -> s == null || s.isEmpty();
System.out.println(isEmpty.test(""));
3.3 扩展的函数式接口(java.util.function 包)
Java 8 提供了超过 40 个函数式接口,针对基本类型做了优化,避免频繁装箱拆箱。
| 分类 | 接口示例 | 抽象方法 |
|---|---|---|
| 消费型 | BiConsumer<T,U> |
void accept(T t, U u) |
| 供给型 | BooleanSupplier |
boolean getAsBoolean() |
| 函数型 | BiFunction<T,U,R> |
R apply(T t, U u) |
| 断言型 | BiPredicate<T,U> |
boolean test(T t, U u) |
| 一元运算 | UnaryOperator<T> |
T apply(T t) |
| 二元运算 | BinaryOperator<T> |
T apply(T t1, T t2) |
| 基本类型 | IntPredicate, LongFunction<R>, DoubleConsumer 等 |
避免装箱 |
// BiPredicate:判断两个字符串长度是否相等
BiPredicate<String, String> lengthEqual = (s1, s2) -> s1.length() == s2.length();
System.out.println(lengthEqual.test("abc", "def")); // true
// IntPredicate:判断整数是否为正
IntPredicate isPositive = n -> n > 0;
System.out.println(isPositive.test(5)); // true
四、变量捕获(Variable Capture)
Lambda 表达式可以访问外部变量,这些变量必须是 effectively final(实际上的 final),即赋值后不再改变。
int num = 10;
Runnable r = () -> System.out.println(num); // 有效,num 未被修改
// 下面的代码会编译错误
int count = 5;
Runnable r2 = () -> System.out.println(count);
count = 6; // count 被修改了,不再是 effectively final
与匿名内部类的区别:
- 匿名内部类要求外部变量必须是
final(Java 8 之前显式 final,之后 effectively final)。 - Lambda 同样要求 effectively final,但没有引入新的作用域(Lambda 中的
this指向外部类的this,而不是自己的实例)。
public class Outer {
private String name = "Outer";
public void test() {
Runnable r = () -> System.out.println(this.name); // 打印 Outer
r.run();
}
}
五、方法引用(Method Reference)
当 Lambda 表达式只是调用一个已有的方法时,可以用方法引用进一步简化。语法是 类名或对象::方法名。
5.1 四种方法引用类型
| 类型 | 语法 | 示例 | 等价的 Lambda |
|---|---|---|---|
| 静态方法引用 | 类名::静态方法 |
Math::max |
(a, b) -> Math.max(a, b) |
| 实例方法引用(特定对象) | 对象::实例方法 |
System.out::println |
x -> System.out.println(x) |
| 实例方法引用(任意对象,第一个参数作为调用者) | 类名::实例方法 |
String::length |
s -> s.length() |
| 构造器引用 | 类名::new |
ArrayList::new |
() -> new ArrayList() |
5.2 静态方法引用
// Lambda
BinaryOperator<Integer> max = (x, y) -> Math.max(x, y);
// 方法引用
BinaryOperator<Integer> maxRef = Math::max;
5.3 特定对象实例方法引用
// 特定对象
PrintStream out = System.out;
Consumer<String> printer = out::println;
printer.accept("Hello");
5.4 任意对象实例方法引用(重点理解)
当 Lambda 的第一个参数是方法的调用者,第二个参数(如果有)是方法的参数时,可以使用此形式。
// Lambda
Function<String, Integer> len = s -> s.length();
BiPredicate<String, String> eq = (a, b) -> a.equals(b);
// 方法引用
Function<String, Integer> lenRef = String::length;
BiPredicate<String, String> eqRef = String::equals;
规则:ClassName::instanceMethod 等价于 (obj, param) -> obj.instanceMethod(param)。
5.5 构造器引用
// 无参构造
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
// 一个参数的构造(Integer 作为参数)
Function<Integer, ArrayList<Integer>> listWithSize = ArrayList::new;
ArrayList<Integer> list2 = listWithSize.apply(10);
// 数组构造器引用
Function<Integer, int[]> arrayCreator = int[]::new;
int[] arr = arrayCreator.apply(5);
注意:构造器引用要求函数式接口的抽象方法参数列表与构造器参数列表一致。
六、Lambda 与集合框架
Java 8 为集合框架增加了大量接受 Lambda 的方法,使代码更简洁。
6.1 forEach
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(s -> System.out.println(s));
// 或使用方法引用
list.forEach(System.out::println);
6.2 removeIf
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(n -> n % 2 == 0); // 移除偶数
System.out.println(numbers); // [1, 3, 5]
6.3 replaceAll
List<String> words = Arrays.asList("hello", "world");
words.replaceAll(s -> s.toUpperCase());
System.out.println(words); // [HELLO, WORLD]
6.4 sort
List<Integer> nums = Arrays.asList(5, 2, 8, 1);
nums.sort((a, b) -> a - b); // 升序
nums.sort(Integer::compareTo); // 方法引用
6.5 Map 新方法
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
// forEach
map.forEach((k, v) -> System.out.println(k + "=" + v));
// computeIfAbsent
map.computeIfAbsent("c", k -> k.length()); // 若 key 不存在则计算 value
System.out.println(map.get("c")); // 1
// merge
map.merge("a", 10, (oldVal, newVal) -> oldVal + newVal);
System.out.println(map.get("a")); // 11
七、Stream API 与 Lambda(简要介绍)
Stream API 是处理集合数据的函数式利器,大量使用 Lambda 表达式。以下是最常见的操作。
7.1 创建 Stream
List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> stream = list.stream();
7.2 中间操作(返回 Stream)
| 操作 | 说明 |
|---|---|
filter(Predicate) |
过滤元素 |
map(Function) |
转换元素 |
sorted(Comparator) |
排序 |
distinct() |
去重 |
limit(n) |
限制个数 |
7.3 终端操作(返回结果)
| 操作 | 说明 |
|---|---|
forEach(Consumer) |
遍历 |
collect(Collector) |
收集到集合 |
reduce(BinaryOperator) |
归约 |
count() |
计数 |
anyMatch(Predicate) |
任一匹配 |
findFirst() |
找第一个 |
// 示例:获取长度大于5的字符串,转成大写,排序,收集为列表
List<String> result = list.stream()
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
八、Lambda 与匿名内部类的区别
| 对比项 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
| 适用对象 | 任意接口/抽象类 | 仅函数式接口 |
| 字节码生成 | 每个匿名类生成一个单独的 .class 文件 |
使用 invokedynamic,运行时生成 |
this 指向 |
匿名类自己的实例 | 外部类的实例 |
| 变量捕获 | 需要 final 或 effectively final |
相同规则 |
| 方法重写 | 可重写多个方法 | 只实现一个抽象方法 |
| 性能 | 类加载开销 | 轻量,类似 invokedynamic |
九、注意事项与常见陷阱
9.1 Lambda 不能独立使用,必须依赖函数式接口
Lambda 表达式本身没有类型,它的类型由上下文(赋值、参数传递)的函数式接口决定。
// 错误:不能单独出现
// Object obj = () -> System.out.println();
// 正确:赋值给函数式接口类型
Runnable r = () -> System.out.println();
9.2 Lambda 内的局部变量必须是 effectively final
int x = 1;
Runnable r = () -> System.out.println(x);
x = 2; // 编译错误,x 不再是 effectively final
9.3 避免过长的 Lambda
如果 Lambda 体很长(超过几行),建议重构为普通方法,然后使用方法引用。
// 不推荐
list.forEach(item -> {
// 很多行逻辑...
});
// 推荐
list.forEach(this::processItem);
9.4 Lambda 中的异常处理
如果 Lambda 体抛出了受检异常,函数式接口的抽象方法必须声明该异常,否则只能用 try-catch。
// IOException 是受检异常
// Consumer.accept 没有 throws,所以需要捕获
list.forEach(s -> {
try {
Files.write(Paths.get(s), "".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
9.5 谨慎使用并行流
并行流(parallelStream())在多线程环境下可能带来线程安全问题,且不是所有操作都能提升性能。
// 可能不正确的使用
List<Integer> list = new ArrayList<>();
IntStream.range(0, 10000).parallel().forEach(list::add); // 线程不安全
System.out.println(list.size()); // 可能小于10000
十、自定义函数式接口示例
如果内置接口不能满足需求,可以自己定义。
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
public class Demo {
public static void main(String[] args) {
TriFunction<Integer, Integer, Integer, Integer> sum = (x, y, z) -> x + y + z;
System.out.println(sum.apply(1, 2, 3)); // 6
}
}
十一、完整示例:使用 Lambda 简化代码
场景:对员工列表进行筛选,找出年龄大于 30 岁的员工,按工资排序,并打印姓名。
class Employee {
private String name;
private int age;
private double salary;
// 构造器、getter、setter 省略
}
public class LambdaDemo {
public static void main(String[] args) {
List<Employee> employees = getEmployees();
employees.stream()
.filter(e -> e.getAge() > 30)
.sorted(Comparator.comparingDouble(Employee::getSalary))
.map(Employee::getName)
.forEach(System.out::println);
}
}
传统方式需要写很多循环、临时集合和比较器,而 Stream + Lambda 让代码变成了声明式,读起来更像问题描述。
十二、知识点结尾
| 知识点 | 要点 |
|---|---|
| Lambda 语法 | (参数) -> { 代码 },可简化省略 |
| 函数式接口 | 只有一个抽象方法的接口,可用 @FunctionalInterface |
| 内置接口 | Consumer, Supplier, Function, Predicate 及其扩展 |
| 变量捕获 | 外部变量必须是 final 或 effectively final |
| 方法引用 | 类::静态方法、对象::实例方法、类::实例方法、类::new |
| 集合增强 | forEach, removeIf, replaceAll, sort, computeIfAbsent |
| Stream API | 链式处理数据,常用中间/终端操作 |
| 注意事项 | 不能独立使用、避免过长、异常处理、并行流安全 |
Lambda 表达式让 Java 具备了函数式编程的灵活性,同时也大大增强了集合处理的表达能力。掌握 Lambda 是学习后续 Stream API、函数式接口、Optional 等特性的基础,也是编写现代 Java 程序必备的技能。多写多练,你会逐渐习惯这种更简洁、更直接的编码风格。
今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文

更多推荐




所有评论(0)