一文徹底搞懂Java中的回調(diào)函數(shù)
在學(xué)習(xí)代理設(shè)計(jì)模式中提到了“回調(diào)”這個(gè)東東,一下子把我打蒙了。于是決心花點(diǎn)小時(shí)間徹底理解java里的回調(diào)是什么。
是什么
定義
回調(diào)(Callback)是 Java 中最基礎(chǔ)、最核心的編程設(shè)計(jì)思想,本質(zhì)是「方法作為參數(shù)傳遞 + 控制權(quán)反轉(zhuǎn)」的實(shí)現(xiàn)。回調(diào)邏輯的執(zhí)行時(shí)機(jī),由被調(diào)用方?jīng)Q定,而非調(diào)用方(控制權(quán)反轉(zhuǎn))
回調(diào) = 調(diào)用方把「自己的邏輯(方法)」交給被調(diào)用方 → 被調(diào)用方在「指定時(shí)機(jī)」主動(dòng)執(zhí)行該邏輯。
好比點(diǎn)外賣時(shí)(場景)我把我的電話號(hào)碼(對(duì)象)交給店鋪(被調(diào)用者),店鋪會(huì)在我的外賣送到時(shí)(指定時(shí)機(jī))給我打電話(對(duì)象可以調(diào)用的方法)。
成員:
心成員 1:「調(diào)用方(Caller)」
? 核心職責(zé):定義回調(diào)邏輯(或持有已有類的回調(diào)方法) + 將回調(diào)載體(接口對(duì)象 / 普通類對(duì)象)傳遞給被調(diào)用方;
? 核心特征:主動(dòng)發(fā)起交互,把自己的邏輯交付出去,不控制回調(diào)執(zhí)行時(shí)機(jī)。
核心成員 2:「被調(diào)用方(Callee)」
? 核心職責(zé):接收回調(diào)載體 + 執(zhí)行自身核心業(yè)務(wù) + 在指定時(shí)機(jī)主動(dòng)調(diào)用回調(diào)載體中的方法;
? 核心特征:擁有「回調(diào)觸發(fā)權(quán)」,決定回調(diào)邏輯什么時(shí)候執(zhí)行,是回調(diào)的主導(dǎo)方。
核心成員 3:「回調(diào)體(Callback Body)」
? 核心定義:封裝了回調(diào)邏輯的載體(可以是「接口實(shí)現(xiàn)類對(duì)象」、也可以是「已有方法的普通類對(duì)象」),是調(diào)用方與被調(diào)用方的「通信橋梁」;
? 核心特征:內(nèi)部包含具體的「回調(diào)方法」,供被調(diào)用方主動(dòng)觸發(fā)。
實(shí)例
同步回調(diào)(對(duì)應(yīng)的異步會(huì)回調(diào)就是在被調(diào)用方內(nèi)部開個(gè)獨(dú)立線程)
// ========== 步驟1:定義回調(diào)接口(約定回調(diào)方法規(guī)范,核心) ==========
interface MyCallback {
// 回調(diào)方法:被調(diào)用方在「任務(wù)完成后」主動(dòng)調(diào)用
void onTaskFinish(String result);
}
// ========== 步驟2:被調(diào)用方:接收回調(diào)對(duì)象,執(zhí)行核心任務(wù)后觸發(fā)回調(diào) ==========
class CallbackTaskService {
// 核心方法:入?yún)椤富卣{(diào)接口對(duì)象」,接收調(diào)用方的邏輯
public void doTask(MyCallback callback) {
System.out.println("【被調(diào)用方】開始執(zhí)行耗時(shí)核心任務(wù)...");
// 模擬核心任務(wù)執(zhí)行(如數(shù)據(jù)庫查詢、網(wǎng)絡(luò)請(qǐng)求)
String taskResult = "核心任務(wù)執(zhí)行成功,返回結(jié)果";
// 關(guān)鍵:被調(diào)用方主動(dòng)觸發(fā)回調(diào)(時(shí)機(jī)由被調(diào)用方?jīng)Q定)
callback.onTaskFinish(taskResult);
}
}
// ========== 步驟3:調(diào)用方:實(shí)現(xiàn)回調(diào)接口,傳遞回調(diào)對(duì)象 ==========
public class CallbackCallDemo {
public static void main(String[] args) {
CallbackTaskService service = new CallbackTaskService();
// 調(diào)用方通過「匿名內(nèi)部類」實(shí)現(xiàn)回調(diào)接口
MyCallback callback = new MyCallback() {
@Override
public void onTaskFinish(String result) {
// 調(diào)用方自定義的回調(diào)邏輯:處理被調(diào)用方返回的結(jié)果
System.out.println("【調(diào)用方-回調(diào)邏輯】收到被調(diào)用方結(jié)果:" + result);
}
};
// 傳遞回調(diào)對(duì)象給被調(diào)用方
service.doTask(callback);
// 核心優(yōu)勢(shì):調(diào)用方無需等待,可立即執(zhí)行自身其他邏輯
System.out.println("【調(diào)用方】發(fā)起任務(wù)后,直接執(zhí)行自身后續(xù)邏輯,無需等待任務(wù)完成");
}
}注意,這里不是必須得有個(gè)接口什么的,這不是一種固定的代碼寫法而是一種設(shè)計(jì)模式。只是使用接口可以實(shí)現(xiàn)強(qiáng)解耦,統(tǒng)一規(guī)范,是最佳實(shí)踐。在實(shí)例代碼的基礎(chǔ)上還可以用lambda簡化等等。
常見應(yīng)用
像代理模式中就有用到,不過里面層層包裝很難看清怎么用的,這里只講講線程 / 線程池回調(diào)。
// 回調(diào)體:Runnable是回調(diào)接口,匿名內(nèi)部類實(shí)現(xiàn)的是「回調(diào)對(duì)象」
Runnable callbackObj = new Runnable() {
@Override
public void run() {
// ?? 回調(diào)方法:存放「用戶自定義的業(yè)務(wù)邏輯」(擴(kuò)展邏輯)
System.out.println("【線程回調(diào)】執(zhí)行線程內(nèi)業(yè)務(wù)邏輯");
}
};
// 被調(diào)用方:Thread類 + JVM(核心邏輯執(zhí)行者,掌握回調(diào)觸發(fā)權(quán))
Thread thread = new Thread(callbackObj);
// 調(diào)用方:我們的主線程代碼(傳遞回調(diào)體、發(fā)起調(diào)用,不控制觸發(fā)時(shí)機(jī))
thread.start(); 執(zhí)行鏈路
1. 主線程調(diào)用 thread.start() → 向JVM發(fā)起「線程啟動(dòng)請(qǐng)求」,線程進(jìn)入「就緒狀態(tài)」;
2. JVM線程調(diào)度器分配CPU資源 → 線程從「就緒」轉(zhuǎn)為「運(yùn)行狀態(tài)」;
3. JVM底層主動(dòng)調(diào)用 Thread類中的run()方法;
4. Thread類的run()方法內(nèi)部,會(huì)調(diào)用「傳入的Runnable對(duì)象」的run()方法;
5. 最終觸發(fā):我們自定義的回調(diào)邏輯(Runnable匿名內(nèi)部類的run())執(zhí)行;
6. 業(yè)務(wù)邏輯執(zhí)行完畢 → 線程進(jìn)入「終止?fàn)顟B(tài)」,回調(diào)完成。
常見應(yīng)用場景
| 應(yīng)用領(lǐng)域 | 具體場景 | 核心回調(diào)接口 / 方法 | 觸發(fā)時(shí)機(jī) |
|---|---|---|---|
| JDK 原生 | 線程 / 線程池 | Runnable.run()/Callable.call() | 線程啟動(dòng)后 |
| JDK 原生 | 異步回調(diào) | CompletableFuture.thenAccept() | 異步任務(wù)完成后 |
| JDK 原生 | 集合遍歷 | Collection.forEach() | 遍歷每個(gè)元素時(shí) |
| Spring 生態(tài) | Bean 生命周期 | InitializingBean.afterPropertiesSet() | Bean 初始化完成后 |
| Spring 生態(tài) | 事件監(jiān)聽 | ApplicationListener.onApplicationEvent() | 發(fā)布事件時(shí) |
| Spring 生態(tài) | MVC 攔截器 | HandlerInterceptor.preHandle() | 請(qǐng)求處理前后 |
| 動(dòng)態(tài)代理 | CGLIB/JDK 代理 | MethodInterceptor.intercept()/InvocationHandler.invoke() | 調(diào)用代理方法時(shí) |
| MyBatis | 類型轉(zhuǎn)換 | TypeHandler.setNonNullParameter() | 參數(shù)設(shè)置 / 結(jié)果映射時(shí) |
| Netty | NIO 事件 | ChannelHandler.channelRead() | IO 事件觸發(fā)時(shí) |
| 定時(shí)任務(wù) | 定時(shí)器 | TimerTask.run() | 定時(shí)時(shí)間到達(dá)時(shí) |
為什么
其實(shí)跟大多數(shù)設(shè)計(jì)模式一樣,還是「傳遞邏輯、移交控制、分離職責(zé)」這些優(yōu)點(diǎn),幫助我們寫出邏輯性更強(qiáng)更簡約的代碼。
不過這里的什么解決代碼緊耦合,設(shè)計(jì)模式里的裝飾器模式,觀察者模式等等都能做到。代碼量太低的我就很難理解里面的精妙之處,特別之處了,這里更多的是記住“這種場景”來選擇使用。
總結(jié)
到此這篇關(guān)于Java中回調(diào)函數(shù)的文章就介紹到這了,更多相關(guān)Java回調(diào)函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java使用jSerialComm進(jìn)行跨平臺(tái)串口通信的完全指南
在現(xiàn)代嵌入式開發(fā)和物聯(lián)網(wǎng)應(yīng)用中,串口通信仍然是設(shè)備間數(shù)據(jù)交換的重要方式,jSerialComm作為一款專為Java設(shè)計(jì)的跨平臺(tái)串口通信庫,為開發(fā)者提供了簡單高效的解決方案,下面小編就和大家詳細(xì)講講具體實(shí)現(xiàn)方法吧2025-12-12
SpringBoot淺析緩存機(jī)制之Redis單機(jī)緩存應(yīng)用
在上文中我介紹了Spring Boot使用EhCache 2.x來作為緩存的實(shí)現(xiàn),本文接著介紹使用單機(jī)版的Redis作為緩存的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
在Java Spring框架中實(shí)現(xiàn)BLOB類型的附件下載的詳細(xì)步驟
在Java Spring框架中實(shí)現(xiàn)BLOB(Binary Large Object)類型的附件下載,通常涉及到幾個(gè)關(guān)鍵步驟,下面將詳細(xì)介紹這些步驟,并結(jié)合具體的代碼示例來說明如何在Spring Boot應(yīng)用程序中實(shí)現(xiàn)這一功能,需要的朋友可以參考下2025-10-10
教你用Java實(shí)現(xiàn)一個(gè)簡單的代碼生成器
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著如何用Java實(shí)現(xiàn)一個(gè)簡單的代碼生成器展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
關(guān)于SpringBoot2.7.6連接nacos遇到的一些問題
這篇文章主要介紹了關(guān)于SpringBoot2.7.6連接nacos遇到的一些問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
五分鐘教你手寫 SpringBoot 本地事務(wù)管理實(shí)現(xiàn)
這篇文章主要介紹了五分鐘教你手寫 SpringBoot 本地事務(wù)管理實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02

