談到回調,咱們得先從回調函數提及,什麼叫回調函數呢?html
百度百科的解釋:回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一個函數,當這個指針被用爲調用它所指向的函數時,咱們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另一方調用的,用於對該事件或條件進行響應。java
接着,咱們從下圖簡單說明一下回調函數。ios
已知圖形上面三種模塊,此時標號2能稱爲回調函數嗎?算法
答案:不能,只有當標號2函數做爲參數傳遞給標號3函數使用時,才能稱爲回調函數。shell
再好比,人(相似函數聲明)、老王(相似函數定義)、學校(相似調用方)三個概念,某學校須要招聘人當教師,這時老王去應聘,因爲老王具備出色的教導能力,學校聘用老王做爲高級教師。被學校成功聘用的老王,此時才能稱爲高級教師(相似回調函數),不然他還只是老王這一個身份,而不能稱爲高級教師。設計模式
回調函數的機制:app
(1)定義一個回調函數;ide
(2)提供函數實現的一方在初始化時候,將回調函數的函數指針註冊給調用者;svg
(3)當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理。函數
回調函數一般與原始調用者處於同一層次,如圖所示:
由於能夠把調用者與被調用者分開。調用者不關心誰是被調用者,全部它需知道的,只是存在一個具備某種特定原型、某些限制條件(如返回值爲int)的被調用函數。
若是想知道回調函數在實際中有什麼做用,先假設有這樣一種狀況,咱們要編寫一個庫,它提供了某些排序算法的實現,如冒泡排序、快速排序、shell排序、shake排序等等,但爲使庫更加通用,不想在函數中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,想讓庫可用於多種數據類型(int、float、string),此時,該怎麼辦呢?可使用函數指針,並進行回調。
回調可用於通知機制,例如,有時要在程序中設置一個計時器,每到必定時間,程序會獲得相應的通知,但通知機制的實現者對咱們的程序一無所知。而此時,就需有一個特定原型的函數指針,用這個指針來進行回調,來通知咱們的程序事件已經發生。實際上,SetTimer() API使用了一個回調函數來通知計時器,並且,萬一沒有提供回調函數,它還會把一個消息發往程序的消息隊列。
另外一個使用回調機制的API函數是EnumWindow(),它枚舉屏幕上全部的頂層窗口,爲每一個窗口調用一個程序提供的函數,並傳遞窗口的處理程序。若是被調用者返回一個值,就繼續進行迭代,不然,退出。EnumWindow()並不關心被調用者在何處,也不關心被調用者用它傳遞的處理程序作了什麼,它只關心返回值,由於基於返回值,它將繼續執行或退出。
使用回調函數,咱們須要作三件事:
一、聲明函數模型
二、定義函數體
三、將回調函數做爲參數傳遞給知足格式的函數,以便系統調用。
例1:一段C語言代碼
#include <iostream>
using namespace std;
// 一、聲明
typedef void (*PF)();
// 二、定義
void func()
{
cout << "func" << endl;
}
void caller( PF pf)
{
pf();
}
int main()
{
PF p = func;
// 三、函數做爲參數傳遞
caller(p);
system("pause");
return 0;
}
using System;
using System.Runtime.InteropServices;
// 一、函數聲明
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()
{
// 三、函數做爲參數傳遞
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}
// 二、函數定義
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
從回調函數的使用說明中,咱們能夠將分爲兩部分:協議和協議的調用
一、協議規範(函數聲明)
二、協議實現(函數定義)
三、調用協議(函數做爲參數傳遞,使用)
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()
{
}
});
}
}
參考: