一文彻底搞懂Java中的回调函数
作者:马猴烧酒.
在学习代理设计模式中提到了“回调”这个东东,一下子把我打蒙了。于是决心花点小时间彻底理解java里的回调是什么。
是什么
定义
回调(Callback)是 Java 中最基础、最核心的编程设计思想,本质是「方法作为参数传递 + 控制权反转」的实现。回调逻辑的执行时机,由被调用方决定,而非调用方(控制权反转)
回调 = 调用方把「自己的逻辑(方法)」交给被调用方 → 被调用方在「指定时机」主动执行该逻辑。
好比点外卖时(场景)我把我的电话号码(对象)交给店铺(被调用者),店铺会在我的外卖送到时(指定时机)给我打电话(对象可以调用的方法)。
成员:
心成员 1:「调用方(Caller)」
▸ 核心职责:定义回调逻辑(或持有已有类的回调方法) + 将回调载体(接口对象 / 普通类对象)传递给被调用方;
▸ 核心特征:主动发起交互,把自己的逻辑交付出去,不控制回调执行时机。
核心成员 2:「被调用方(Callee)」
▸ 核心职责:接收回调载体 + 执行自身核心业务 + 在指定时机主动调用回调载体中的方法;
▸ 核心特征:拥有「回调触发权」,决定回调逻辑什么时候执行,是回调的主导方。
核心成员 3:「回调体(Callback Body)」
▸ 核心定义:封装了回调逻辑的载体(可以是「接口实现类对象」、也可以是「已有方法的普通类对象」),是调用方与被调用方的「通信桥梁」;
▸ 核心特征:内部包含具体的「回调方法」,供被调用方主动触发。
实例
同步回调(对应的异步会回调就是在被调用方内部开个独立线程)
// ========== 步骤1:定义回调接口(约定回调方法规范,核心) ==========
interface MyCallback {
// 回调方法:被调用方在「任务完成后」主动调用
void onTaskFinish(String result);
}
// ========== 步骤2:被调用方:接收回调对象,执行核心任务后触发回调 ==========
class CallbackTaskService {
// 核心方法:入参为「回调接口对象」,接收调用方的逻辑
public void doTask(MyCallback callback) {
System.out.println("【被调用方】开始执行耗时核心任务...");
// 模拟核心任务执行(如数据库查询、网络请求)
String taskResult = "核心任务执行成功,返回结果";
// 关键:被调用方主动触发回调(时机由被调用方决定)
callback.onTaskFinish(taskResult);
}
}
// ========== 步骤3:调用方:实现回调接口,传递回调对象 ==========
public class CallbackCallDemo {
public static void main(String[] args) {
CallbackTaskService service = new CallbackTaskService();
// 调用方通过「匿名内部类」实现回调接口
MyCallback callback = new MyCallback() {
@Override
public void onTaskFinish(String result) {
// 调用方自定义的回调逻辑:处理被调用方返回的结果
System.out.println("【调用方-回调逻辑】收到被调用方结果:" + result);
}
};
// 传递回调对象给被调用方
service.doTask(callback);
// 核心优势:调用方无需等待,可立即执行自身其他逻辑
System.out.println("【调用方】发起任务后,直接执行自身后续逻辑,无需等待任务完成");
}
}注意,这里不是必须得有个接口什么的,这不是一种固定的代码写法而是一种设计模式。只是使用接口可以实现强解耦,统一规范,是最佳实践。在实例代码的基础上还可以用lambda简化等等。
常见应用
像代理模式中就有用到,不过里面层层包装很难看清怎么用的,这里只讲讲线程 / 线程池回调。
// 回调体:Runnable是回调接口,匿名内部类实现的是「回调对象」
Runnable callbackObj = new Runnable() {
@Override
public void run() {
// 👉 回调方法:存放「用户自定义的业务逻辑」(扩展逻辑)
System.out.println("【线程回调】执行线程内业务逻辑");
}
};
// 被调用方:Thread类 + JVM(核心逻辑执行者,掌握回调触发权)
Thread thread = new Thread(callbackObj);
// 调用方:我们的主线程代码(传递回调体、发起调用,不控制触发时机)
thread.start(); 执行链路
1. 主线程调用 thread.start() → 向JVM发起「线程启动请求」,线程进入「就绪状态」;
2. JVM线程调度器分配CPU资源 → 线程从「就绪」转为「运行状态」;
3. JVM底层主动调用 Thread类中的run()方法;
4. Thread类的run()方法内部,会调用「传入的Runnable对象」的run()方法;
5. 最终触发:我们自定义的回调逻辑(Runnable匿名内部类的run())执行;
6. 业务逻辑执行完毕 → 线程进入「终止状态」,回调完成。
常见应用场景
| 应用领域 | 具体场景 | 核心回调接口 / 方法 | 触发时机 |
|---|---|---|---|
| JDK 原生 | 线程 / 线程池 | Runnable.run()/Callable.call() | 线程启动后 |
| JDK 原生 | 异步回调 | CompletableFuture.thenAccept() | 异步任务完成后 |
| JDK 原生 | 集合遍历 | Collection.forEach() | 遍历每个元素时 |
| Spring 生态 | Bean 生命周期 | InitializingBean.afterPropertiesSet() | Bean 初始化完成后 |
| Spring 生态 | 事件监听 | ApplicationListener.onApplicationEvent() | 发布事件时 |
| Spring 生态 | MVC 拦截器 | HandlerInterceptor.preHandle() | 请求处理前后 |
| 动态代理 | CGLIB/JDK 代理 | MethodInterceptor.intercept()/InvocationHandler.invoke() | 调用代理方法时 |
| MyBatis | 类型转换 | TypeHandler.setNonNullParameter() | 参数设置 / 结果映射时 |
| Netty | NIO 事件 | ChannelHandler.channelRead() | IO 事件触发时 |
| 定时任务 | 定时器 | TimerTask.run() | 定时时间到达时 |
为什么
其实跟大多数设计模式一样,还是「传递逻辑、移交控制、分离职责」这些优点,帮助我们写出逻辑性更强更简约的代码。
不过这里的什么解决代码紧耦合,设计模式里的装饰器模式,观察者模式等等都能做到。代码量太低的我就很难理解里面的精妙之处,特别之处了,这里更多的是记住“这种场景”来选择使用。
总结
到此这篇关于Java中回调函数的文章就介绍到这了,更多相关Java回调函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
