Lambda的设计是为了代码的优雅,要想理解Lambda,得从很开始讲起。
下面是一句话理解部分,如果你看到这一句话后会了,那后面都长篇大论就不用看了。
lambda作用是减少一次性实现接口/匿名内部类的冗余,是对匿名内部类的简化,本质是一个“可传递的行为(函数)。

1 从接口开始的最佳实践
我们都知道,职责划分核心思想是:任务逻辑和执行逻辑彻底解耦,接口定义能力,实现类做业务,执行器做流程控制,装饰器做业务增强增强。
当我们刚开始使用接口时,可能会这样
// 文件一:任务接口
interface Task {
void execute();
}
// 文件二:实现Task的类
class MyTask implements Task {
// 实现类的方法
@Override
public void execute() {
System.out.println("任务执行了!");
}
//
public static void main(String[] args) {
Test test = new Test();
task.execute();
}
}
- 不知道你有没有意识到一件事?这段代码,不优雅,因为:
- 没有复用性
- 无法统一添加执行前后的逻辑
- 控制权和任务逻辑混杂在一起
可能你暂时看不出来?那看看经过注释后的类吧
class MyTask implements Task {
// 实现类的方法
@Override
public void execute() {
System.out.println("任务执行了!");
}
public static void main(String[] args) {
Test test = new Test();
//第一次调用,我得把调用前后逻辑直接加在这里
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
//其他代码
// 第二次调用,又得重复前后逻辑,这就是没有复用性
System.out.println("执行前的逻辑");
task.execute();// 任务逻辑
System.out.println("执行后的逻辑");
// 每次调用task.execute()都需要添加执行前的逻辑和执行后的逻辑,因此,main方法即当妈,又当爸,一边控制任务什么时候执行,一边编写着任务逻辑
}
}
- 相信这下你该明白代码为什么不优雅了,那如何解决这个不优雅呢?你可能会想,可以这样写
class MyTask implements Task {
// 实现类的方法
@Override
public void execute() {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
}
public static void main(String[] args) {
Test test = new Test();
task.execute();
// 其他代码
task.execute();
}
}
- 乍一看,是少了很多代码,但好像还有些问题?如果我有两种任务逻辑呢?
// 任务逻辑1
public void execute() {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
}
// 任务逻辑2
public void execute() {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
}
- 很显然,这样写根本没有解决问题!甚至无法实现两种任务逻辑。更好的解决方法是下面这样
// 文件一:任务接口
interface Task {
void execute();
}
// 文件二:实现了这个接口的类
class MyTask implements Task {
@Override
public void execute() {
System.out.println("任务执行了!");
}
public static void main(String[] args) {
Test test = new Test();
runTask(test); //现在就可以调用任意次数了
// 其他代码
runTask(test);
}
public static void runTask(Task task) {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
}
public static void runTask1(Task task) {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
}
}
// 如果你读过设计模式,应该会感觉到 runTask,runTask1是手动实现装饰器,且只像个增强行为,真正的装饰器不应该这么写。本文只是为了理解Lambda,而如此设计案例
- 就如文章一开始说的一样:**任务逻辑和执行逻辑彻底解耦,接口定义能力,实现类做业务,执行器做流程控制,装饰器做业务增强增强。**这段代码,在小的地方完美了,但如果我们将目光放大到类上呢?就会注意到 class MyTask implements Task。
- MyTask 同时承担了业务逻辑和程序控制(main 方法),即它既是业务类,也是执行器。
更好的方法是:
// 文件一:任务接口
interface Task {
void execute();
}
// 文件二:业务实现类,只关注这个业务是怎么执行的
class MyTask implements Task {
@Override
public void execute() {
System.out.println("任务执行了!");
}
}
// 文件三:执行器类,只做流程控制
class TaskExecutor {
public static void runTask(Task task) {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
}
}
// 文件四:程序入口,只负责调用
public class Main {
public static void main(String[] args) {
Task myTask = new MyTask();
TaskExecutor.runTask(myTask);
TaskExecutor.runTask(myTask); // 可以任意调用
}
}
- 现在,这段代码很完美的。但有足足四个文件!如果有YourTask业务,那还会多出一个类……或者,有的事情只需要执行一次,然后就完全不用了。这些场景,要怎么解决?
1.1 如何面对临时任务?
答案:使用内部匿名类
// 文件一:任务接口
interface Task {
void execute();
}
// 文件二:执行器类,只做流程控制
class TaskExecutor {
public static void runTask(Task task) {
System.out.println("执行前的逻辑");
task.execute(); // 任务逻辑
System.out.println("执行后的逻辑");
}
}
// 文件三:程序入口,只负责调用
public class Main {
public static void main(String[] args) {
// 使用匿名内部类实现 Task
Task myTask = new Task() {
@Override
public void execute() {
System.out.println("任务执行了!");
}
};
TaskExecutor.runTask(myTask);
TaskExecutor.runTask(myTask); // 可以任意调用
}
}
- 通过匿名内部类,我们成功将MyTask类变成了程序入口中的几行代码,那么这些和Lambda有什么关系呢?
2 Lambda
请循其本,文章的一开始就说了:
Lambda 作用是:减少一次性实现接口/匿名内部类的冗余,是对匿名内部类的简化,本质是一个“可传递的行为(函数)。
是的,Lambda是语法糖,为了将代码继续简化,而出现的东西。使用了Lambda,原本的代码
// 使用匿名内部类实现 Task
Task myTask = new Task() {
@Override
public void execute() {
System.out.println("任务执行了!");
}
};
可以变成
Task myTask = () -> System.out.println("任务执行了!");
| 参数列表 | () | Task.execute() 没有参数,所以空括号 |
| 箭头 | -> | 分隔符,把“参数”和“方法体”分开 |
| 方法体 | System.out.println("任务执行了!") | 你想执行的代码块 |
2.1 在内存中
| 局部变量引用 | Task myTask = … | 栈帧中的局部变量表存放引用,指向 Lambda 对象或缓存的单例 Lambda |
| 方法体 | () -> System.out.println("任务执行了!") | Lambda 逻辑,编译器生成静态方法存放在方法区 / 元空间,不占堆 |
| Lambda对象 | 仅当捕获外部变量或需要状态时存在 | 堆上对象,用来保存捕获的外部变量或状态;无状态 Lambda JVM 可复用或不分配堆 |
| 捕获变量 | int x = 10; Task t = () -> System.out.println(x); | 外部局部变量值被复制到 Lambda 对象中(effectively final),对象存放堆或复用缓存 |
| invokedynamic | 字节码调用指令 | 运行期动态生成 Lambda 实现,绑定方法体和接口,JVM 使用方法句柄执行 |
2.2 适用条件
2.2.3 目标类型必须是函数式接口(必须满足)
@FunctionalInterface
interface Task {
void execute();
}
- 只能有一个抽象方法,默认方法 / static 方法不算
- 多个抽象方法 → 不能用 Lambda
2.3 从设计角度来说
适合:
() -> System.out.println("执行任务");
不适合:
() -> {
// 100 行复杂逻辑
// 多层 if / try-catch
}
复杂逻辑使用外部类更清晰
| 读取外部局部变量 | 允许 |
| 修改外部局部变量 | 不允许 |
| 在 Lambda 内声明局部变量 | 允许 |
| 在多次执行间保留状态 | 不允许 |
| 定义成员变量 | 不允许 |
2.4 使用场景
1:一次性策略 / 临时规则
Collections.sort(list, (a, b) -> b – a);
特点:
- 规则只用一次
- 没有业务命名价值
2:回调 / 事件处理
executor.submit(() -> doWork());
button.onClick(() -> save());
3:遍历与流式处理
list.forEach(item -> System.out.println(item));
list.stream()
.filter(x -> x > 10)
.map(x -> x * 2)
.forEach(System.out::println);
4:模板方法 / 执行器的“可变步骤”
TaskExecutor.runTask(() -> System.out.println("任务执行了"));
2.5 源码
- 相关源码在 java.lang.invoke.LambdaMetafactory,可以自己读读。
3 更简洁的语法糖:方法引用
Lambda,已经很简单了,那能不能更简单呢?答案是 : 用方法引用
有一个需求,给list做排序,需要怎么做呢?
使用匿名内部类
// 使用匿名内部类
public class SortExampleAnonymous {
public static void main(String[] args) {
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
names.sort(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
System.out.println(names);
}
}
使用Lambda表达式
public class SortExampleLambda {
public static void main(String[] args) {
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
names.sort((s1, s2) -> s1.compareTo(s2));
System.out.println(names);
}
}
使用方法引用
public class SortExampleMethodRef {
public static void main(String[] args) {
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
// 方法引用
names.sort(String::compareToIgnoreCase);
// 需要满足:
// 1. 引用处需要是函数式接口
// 2. 被引用的方法需要已经存在
// 3. 被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
// 4. 被引用方法的功能需要满足当前的要求
System.out.println(names);
}
}
- String::compareToIgnoreCase的意思是使用Stringi类下的compareToIgnoreCase,方法引用写在括号里的方法一定要是已有的方法。就如下面这样
public class SortExampleCustomMethod {
public static void main(String[] args) {
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
// 使用自定义方法进行排序
names.sort(SortExampleCustomMethod::compareIgnoreCase);
System.out.println(names);
}
// 自定义比较方法
public static int compareIgnoreCase(String s1, String s2) {
return s1.toLowerCase().compareTo(s2.toLowerCase());
}
}
- 方法引用对JavaAPI了解要求比较高
| 静态方法引用 | ClassName::staticMethod | 参数顺序和类型要与函数式接口的抽象方法匹配 |
| 实例方法引用(特定对象) | instance::instanceMethod | 接口方法的参数列表要与实例方法匹配(不包含对象本身) |
| 类名引用实例方法 | ClassName::instanceMethod | 接口方法的第一个参数作为调用对象,其余参数作为方法参数 |
| 构造器引用 | ClassName::new | 接口方法的参数列表要与构造函数参数匹配,返回类型是对象类型 |
-
方法引用可以用在 函数式接口(Functional Interface)的上下文中,也就是说接口中必须 只有一个抽象方法。
-
方法引用不能修改方法签名,它只是 Lambda 的简写。
-
方法引用比 Lambda 简洁,但不适合复杂逻辑。
-
参数类型可以通过上下文推断,所以通常不需要显示写出。
-
lambda在我眼里算能接受的而方法引用抽象的太深了,也不是本文重点,不多介绍。
网硕互联帮助中心





评论前必须登录!
注册