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