深入淺出Java回調(diào)機制
前幾天看了一下Spring的部分源碼,發(fā)現(xiàn)回調(diào)機制被大量使用,覺得有必要把Java回調(diào)機制的理解歸納總結(jié)一下,以方便在研究類似于Spring源碼這樣的代碼時能更加得心應(yīng)手。
注:本文不想扯很多拗口的話來充場面,我的目的是希望以最簡明扼要的語言將Java回調(diào)的大概機制說清楚。好了,言歸正傳。
一句話,回調(diào)是一種雙向調(diào)用模式,什么意思呢,就是說,被調(diào)用方在被調(diào)用時也會調(diào)用對方,這就叫回調(diào)。“If you call me, i will call back”。
不理解?沒關(guān)系,先看看這個可以說比較經(jīng)典的使用回調(diào)的方式:
class A實現(xiàn)接口InA ——背景1
class A中包含一個class B的引用b ——背景2
class B有一個參數(shù)為InA的方法test(InA a) ——背景3
A的對象a調(diào)用B的方法傳入自己,test(a) ——這一步相當(dāng)于you call me
然后b就可以在test方法中調(diào)用InA的方法 ——這一步相當(dāng)于i call you back
是不是清晰一點了?下面再來看一個完全符合這個方式模板的例子
(PS:這個例子來源于網(wǎng)絡(luò),由于這個例子表現(xiàn)的功能極度拉風(fēng),令我感覺想想出一個超越它的例子確實比較困難,所以直接搬過來)
//相當(dāng)于接口InA public interface BoomWTC{ //獲得拉登的決定 public benLaDengDecide(); // 執(zhí)行轟炸世貿(mào) public void boom(); } //相當(dāng)于class A public class At$911 implements BoomWTC{//相當(dāng)于【背景1】 private boolean decide; private TerroristAttack ta;//相當(dāng)于【背景2】 public At$911(){ Date now=new Date(); SimpleDateFormat myFmt1=new SimpleDateFormat("yy/MM/dd HH:mm"); this.dicede= myFmt.format(dt).equals("01/09/11 09:44"); this.ta=new TerroristAttack(); } //獲得拉登的決定 public boolean benLaDengDecide(){ return decide; } // 執(zhí)行轟炸世貿(mào) public void boom(){ ta.attack(new At$911);//class A調(diào)用class B的方法傳入自己的對象,相當(dāng)于【you call me】 } } //相當(dāng)于class B public class TerroristAttack{ public TerroristAttack(){ } public attack(BoomWTC bmw){——這相當(dāng)于【背景3】 if(bmw.benLaDengDecide()){//class B在方法中回調(diào)class A的方法,相當(dāng)于【i call you back】 //let's go......... } } } |
現(xiàn)在應(yīng)該對回調(diào)有一點概念了吧。
可是問題來了,對于上面這個例子來說,看不出用回調(diào)有什么好處,直接在調(diào)用方法不就可以了,為什么要使用回調(diào)呢?
事實上,很多需要進(jìn)行回調(diào)的操作是比較費時的,被調(diào)用者進(jìn)行費時操作,然后操作完之后將結(jié)果回調(diào)給調(diào)用者??催@樣一個例子:
//模擬Spring中HibernateTemplate回調(diào)機制的代碼 interface CallBack{ public void doCRUD(); } public class HibernateTemplate { public void execute(CallBack action){ getConnection(); action.doCRUD(); releaseConnection(); } public void add(){ execute(new CallBack(){ public void doCRUD(){ System.out.println("執(zhí)行add操作..."); } }); } public void getConnection(){ System.out.println("獲得連接..."); } public void releaseConnection(){ System.out.println("釋放連接..."); } } |
可能上面這個例子你不能一眼看出個所以然來,因為其實這里A是作為一個內(nèi)部匿名類存在的。好,不要急,讓我們把這個例子來重構(gòu)一下:
interface CallBack{ //相當(dāng)于接口InA public void doCRUD(); } public class A implements CallBack{//【背景1】 private B b;//【背景2】 public void doCRUD(){ System.out.println("執(zhí)行add操作..."); } public void add(){ b.execute(new A());//【you call me】 } } public class B{ public void execute(CallBack action){ //【背景3】 getConnection(); action.doCRUD(); //【i call you back】 releaseConnection(); } public void getConnection(){ System.out.println("獲得連接..."); } public void releaseConnection(){ System.out.println("釋放連接..."); } } |
好了,現(xiàn)在就明白多了吧,完全可以轉(zhuǎn)化為上面所說的回調(diào)使用方式的模板。
現(xiàn)在在來看看為什么要使用回調(diào),取得連接getConnection();是費時操作,A希望由B來進(jìn)行這個費時的操作,執(zhí)行完了之后通知A即可(即所謂的i call you back)。這就是這里使用回調(diào)的原因。
在網(wǎng)上看到了一個比喻,覺得很形象,這里借用一下:
你有一個復(fù)雜的問題解決不了,打電話給你的同學(xué),你的同學(xué)說可以解決這個問題,但是需要一些時間,那么你不可能一直拿著電話在那里等,你會把你的電話號碼告訴他,讓他解決之后打電話通知你?;卣{(diào)就是體現(xiàn)在你的同學(xué)又反過來撥打你的號碼。
結(jié)合到前面所分析的,你打電話給你同學(xué)就是【you call me】,你同學(xué)解決完之后打電話給你就是【i call you back】。
怎么樣,現(xiàn)在理解了吧?
---------------------------------以下為更新----------------------------------
看了有些朋友的回帖,我又思考了一下,感覺自己之前對回調(diào)作用的理解的確存在偏差。
下面把自己整理之后的想法共享一下,如果有錯誤希望指出!多謝!
先說上面這段代碼,本來完全可以用模板模式來進(jìn)行實現(xiàn):
public abstract class B{ public void execute(){ getConnection(); doCRUD(); releaseConnection(); } public abstract void doCRUD(); public void getConnection(){ System.out.println("獲得連接..."); } public void releaseConnection(){ System.out.println("釋放連接..."); } } public class A extends B{ public void doCRUD(){ System.out.println("執(zhí)行add操作..."); } public void add(){ doCRUD(); } } public class C extends B{ public void doCRUD(){ System.out.println("執(zhí)行delete操作..."); } public void delete(){ doCRUD(); } } 如果改為回調(diào)實現(xiàn)是這樣的: interface CallBack{ public void doCRUD(); } public class HibernateTemplate { public void execute(CallBack action){ getConnection(); action.doCRUD(); releaseConnection(); } public void add(){ execute(new CallBack(){ public void doCRUD(){ System.out.println("執(zhí)行add操作..."); } }); } public void delete(){ execute(new CallBack(){ public void doCRUD(){ System.out.println("執(zhí)行delete操作..."); } }); } public void getConnection(){ System.out.println("獲得連接..."); } public void releaseConnection(){ System.out.println("釋放連接..."); } } |
可見摒棄了繼承抽象類方式的回調(diào)方式更加簡便靈活。不需要為了實現(xiàn)抽象方法而總是繼承抽象類,而是只需要通過回調(diào)來增加一個方法即可,更加的直觀簡潔靈活。這算是回調(diào)的好處之一。
下面再給出一個關(guān)于利用回調(diào)配合異步調(diào)用的很不錯的例子
回調(diào)接口:
public interface CallBack { /** * 執(zhí)行回調(diào)方法 * @param objects 將處理后的結(jié)果作為參數(shù)返回給回調(diào)方法 */ public void execute(Object... objects ); } 消息的發(fā)送者: /** * 這個類相當(dāng)于你自己 */ public class Local implements CallBack,Runnable{ private Remote remote; /** * 發(fā)送出去的消息 */ private String message; public Local(Remote remote, String message) { super(); this.remote = remote; this.message = message; } /** * 發(fā)送消息 */ public void sendMessage() { /**當(dāng)前線程的名稱**/ System.out.println(Thread.currentThread().getName()); /**創(chuàng)建一個新的線程發(fā)送消息**/ Thread thread = new Thread(this); thread.start(); /**當(dāng)前線程繼續(xù)執(zhí)行**/ System.out.println("Message has been sent by Local~!"); } /** * 發(fā)送消息后的回調(diào)函數(shù) */ public void execute(Object... objects ) { /**打印返回的消息**/ System.out.println(objects[0]); /**打印發(fā)送消息的線程名稱**/ System.out.println(Thread.currentThread().getName()); /**中斷發(fā)送消息的線程**/ Thread.interrupted(); } public static void main(String[] args) { Local local = new Local(new Remote(),"Hello"); local.sendMessage(); } public void run() { remote.executeMessage(message, this); //這相當(dāng)于給同學(xué)打電話,打完電話之后,這個線程就可以去做其他事情了,只不過等到你的同學(xué)打回電話給你的時候你要做出響應(yīng) } } |
消息的接收者:
/** * 這個類相當(dāng)于你的同學(xué) */ public class Remote { /** * 處理消息 * @param msg 接收的消息 * @param callBack 回調(diào)函數(shù)處理類 */ public void executeMessage(String msg,CallBack callBack) { /**模擬遠(yuǎn)程類正在處理其他事情,可能需要花費許多時間**/ for(int i=0;i<1000000000;i++) { } /**處理完其他事情,現(xiàn)在來處理消息**/ System.out.println(msg); System.out.println("I hava executed the message by Local"); /**執(zhí)行回調(diào)**/ callBack.execute(new String[]{"Nice to meet you~!"}); //這相當(dāng)于同學(xué)執(zhí)行完之后打電話給你 } } |
由上面這個例子可見,回調(diào)可以作為異步調(diào)用的基礎(chǔ)來實現(xiàn)異步調(diào)用。
posted on 2014-07-15 10:28 順其自然EVO 閱讀(152) 評論(0) 編輯 收藏 所屬分類: 測試學(xué)習(xí)專欄