回調函數
談到回調,我們得先從回調函數說起,什么叫回調函數呢?
回調函數是什么?
百度百科的解釋:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外一方調用的,用於對該事件或條件進行響應。
接着,我們從下圖簡單說明一下回調函數。
已知圖形上面三種模塊,此時標號2能稱為回調函數嗎?
答案:不能,只有當標號2函數作為參數傳遞給標號3函數使用時,才能稱為回調函數。
再比如,人(類似函數聲明)、老王(類似函數定義)、學校(類似調用方)三個概念,某學校需要招聘人當教師,這時老王去應聘,由於老王具有出色的教導能力,學校聘用老王作為高級教師。被學校成功聘用的老王,此時才能稱為高級教師(類似回調函數),否則他還只是老王這一個身份,而不能稱為高級教師。
回調函數的機制:
(1)定義一個回調函數;
(2)提供函數實現的一方在初始化時候,將回調函數的函數指針注冊給調用者;
(3)當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理。
回調函數通常與原始調用者處於同一層次,如圖所示:
為什么使用回調函數?
因為可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,只是存在一個具有某種特定原型、某些限制條件(如返回值為int)的被調用函數。
如果想知道回調函數在實際中有什么作用,先假設有這樣一種情況,我們要編寫一個庫,它提供了某些排序算法的實現,如冒泡排序、快速排序、shell排序、shake排序等等,但為使庫更加通用,不想在函數中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,想讓庫可用於多種數據類型(int、float、string),此時,該怎么辦呢?可以使用函數指針,並進行回調。
回調可用於通知機制,例如,有時要在程序中設置一個計時器,每到一定時間,程序會得到相應的通知,但通知機制的實現者對我們的程序一無所知。而此時,就需有一個特定原型的函數指針,用這個指針來進行回調,來通知我們的程序事件已經發生。實際上,SetTimer() API使用了一個回調函數來通知計時器,而且,萬一沒有提供回調函數,它還會把一個消息發往程序的消息隊列。
另一個使用回調機制的API函數是EnumWindow(),它枚舉屏幕上所有的頂層窗口,為每個窗口調用一個程序提供的函數,並傳遞窗口的處理程序。如果被調用者返回一個值,就繼續進行迭代,否則,退出。EnumWindow()並不關心被調用者在何處,也不關心被調用者用它傳遞的處理程序做了什么,它只關心返回值,因為基於返回值,它將繼續執行或退出。
如何使用回調函數?
使用回調函數,我們需要做三件事:
1、聲明函數模型
2、定義函數體
3、將回調函數作為參數傳遞給滿足格式的函數,以便系統調用。
例1:一段C語言代碼
#include <iostream>
using namespace std;
// 1、聲明
typedef void (*PF)();
// 2、定義
void func()
{
cout << "func" << endl;
}
void caller( PF pf)
{
pf();
}
int main()
{
PF p = func;
// 3、函數作為參數傳遞
caller(p);
system("pause");
return 0;
}
using System;
using System.Runtime.InteropServices;
// 1、函數聲明
public delegate bool CallBack(int hwnd, int lParam);
public class EnumReportApp
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main()
{
// 3、函數作為參數傳遞
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}
// 2、函數定義
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
從回調函數的使用說明中,我們可以將分為兩部分:協議和協議的調用
1、協議規范(函數聲明)
2、協議實現(函數定義)
3、調用協議(函數作為參數傳遞,使用)
接口回調
Java是一門面向對象語言,一切皆對象,因此在Java中不存在回調函數這一說法的。由於Java的一切皆對象性質,從而將回調函數這個特性提升到了接口回調。
接口回調是什么?
接口回調:可以把使用某一接口的類創建的對象的引用賦給該接口聲明的接口變量,那么該接口變量就可以調用被類實現的接口的方法。實際上,當接口變量調用被類實現的接口中的方法時,就是通知相應的對象調用接口的方法,這一過程稱為對象功能的接口回調。
從概念可以看出,接口回調是指一個使用過程,並強調是關於對象功能的使用過程,既然是功能,功能一般就對應着方法體(函數),因此它同樣滿足與回調函數相似的模型。
(1)接口的定義
(2)接口的實現
(3)調用接口
將(2)的引用(地址)傳遞給(3),然后(3)調用(2)中的方法,這一過程稱為對象功能的接口回調。
接口回調與回調函數不同點:接口回調注重的是過程,而回調函數強調的是函數(實體)。
接口回調與回調函數相同點:都是將自身地址傳遞給調用者,讓調用者根據地址調用相關的方法。
關於回調的個人簡單理解就是:將你本身的地址傳給我,我根據你的地址去調用你。
接口回調的機制與回調函數的機制類似:
(1)定義一個接口;
(2)提供接口實現的一方在初始化的時候,將接口回調的引用注冊給調用者;
(3)當特定的事件或條件發生的時候,調用者使用引用調用實現的接口方法對事件進行處理。
接口回調的好處與回調函數的使用類似,在此就不重復介紹。
如何使用接口回調?
接口回調,我將其分為兩種方式,一種推模式,一種為拉模式。
推模式
接口回調的推模式,指的是,甲方主動將其地址推送給調用者。比如下例:
接口:
public interface GasListener接口實現的甲方:
{
public void offerGas(String msg);
}
public class GasCompany implements GasListener調用者:
{
public void advertiseTo(IndoorsMan man)
{
System.out.println("煤氣公司:這是我們的聯系方式,歡迎來電!");
man.setListener(this);
}
@Override
public void offerGas(String msg)
{
System.out.println("煤氣公司接收的訂單:"+msg);
}
}
public class IndoorsMan測試:
{
GasListener gListener;
public void prepareCook()
{
System.out.println("宅男:准備下廚做幾個花式大菜!");
System.out.println("宅男:進廚房,燒菜...");
System.out.println("宅男:剛開火,就發現煤氣不足,沒辦法,只能打電話叫煤氣。");
gListener.offerGas("宅男:送一瓶煤氣過來!");
}
public void setListener(GasListener gListener)
{
this.gListener = gListener;
}
}
public class TestGasCompany公司在打廣告時,就主動把自身信息告訴了IndoorsMan,當IndoorsMan發現煤氣不足這一事件發生時,IndoorsMan就根據接口的引用調用GasCompany服務。
{
public static void main(String[] args)
{
IndoorsMan man = new IndoorsMan();
GasCompany company = new GasCompany();
company.advertiseTo(man);
man.prepareCook();
}
}
調用者
public class IndoorsMan
{
GasListener gListener;
public void prepareCook()
{
System.out.println("宅男:准備下廚做幾個花式大菜!");
System.out.println("宅男:進廚房,燒菜...");
System.out.println("宅男:剛開火,就發現煤氣不足,沒辦法,只能打電話叫煤氣。");
gListener.offerGas("宅男:送一瓶煤氣過來!");
}
public void setListener(GasListener gListener)
{
this.gListener = gListener;
}
public void configureGas()
{
// 主動獲取甲方信息
setListener(new GasListener()
{
@Override
public void offerGas(String msg)
{
System.out.println("下單內容:"+msg);
}
});
}
}
public class Test
{
public static void main(String[] args)
{
IndoorsMan man = new IndoorsMan();
man.configureGas();
man.prepareCook();
}
}
public class Test
{
public static void main(String[] args)
{
Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
}
});
}
}
參考: