理解Java回調機制

其實對於回調機制,在實際使用中還是經常用到的。但好笑的是,一直沒能對所謂的回調的概念有一個很清晰的理解。

最近抽空看一些書的時候,老是時不時的提到回調的概念。那好吧,正好抽空來簡單總結總結,加深一下印象和理解~


網上的百科之類的資料中,看到的對於回調比較書面和規範的解釋是:

在計算機程序設計中,回調函數是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計允許了底層代碼調用在高層定義的子程序。


不知道別人對於這樣比較“官方的”概念是一種怎麼樣的感受,反正我是“一頭包”,個人十分討厭這種書面解釋。

於是又在網上看了一些別人寫的關於回調的文章,發現很久以前就有別人作了一個很形象的比喻:

某天,我打電話向你請教問題,當然是個難題,^_^,你一時想不出解決方法,我又不能拿着電話在那裏傻等。

於是我們約定:等你想出辦法後打手機通知我,這樣,我就掛掉電話辦其它事情去了。

過了XX分鐘,我的手機響了,你興高采烈的說問題已經搞定,應該如此這般處理。故事到此結束


這個例子說明了“異步+回調”的編程模式。其中,你後來打手機告訴我結果便是一個“回調”過程;

我的手機號碼必須在之前就告訴你,這便是註冊回調函數;我的手機號碼應該有效並且手機能夠接收到你的呼叫,這是回調函數必須符合接口規範。


而該作者針對這一情況給出的例子如下,假設我是程序員A,以下是我的程序a:

public class Caller  
{  
    public MyCallInterface mc;  
  
    public void setCallfuc(MyCallInterface mc)  
    {  
       this.mc= mc;  
    }  
  
    public void call(){  
       this.mc.method();  
    }  
}  

我還需要定義一個接口,以便程序員B根據我的定義編寫程序實現接口。
    public interface MyCallInterface  
    {  
        public void method();  
      
    }  


於是,程序員B只需要實現這個接口就能達到回調的目的了:

    public class B implements MyCallInterface  
    {  
        public void method()  
        {  
           System.out.println("回調");  
        }  
      
        public static void main(String args[])  
        {  
           Caller call = new Caller();  
           call.setCallfuc(new B());  
           call.call();  
        }  
    }  

看完這個例子過後,對於回調似乎有一定的理解了,但似乎卻還是處於一種似懂非懂的狀態,蛋疼中...

於是又繼續查找了一些資料,然後發現,上面的例子對於“回調”思想體現的重點大致在於:

在Java當中,一般來說類的成員變量一般都是數據對象,主要是用來傳遞數據用的。

而回調的意思是:把一段程序作爲成員變量,在特定的場合使用該段程序。這就是回調的核心

這就剛好對應了上面講到的,網上的百科資料中對於回調比較“書面”和“官方”的解釋了。


所以,大致來講對於上面的例子中:

程序員A就是想要把一段程序,也就是函數“method”作爲成員變量來使用;但是呢,Java的語言特性導致了一段程序是不能單獨作爲變量和參數來使用的。

那麼,基於面向對象的特性,就只能將其封裝成類也就是對象來使用。但是這段代碼在程序員A進行編程的時候又是無法確定的,它只能提供一個入口(即聲明),讓B去實現。

這種特點恰恰適合於Java當中Interface的特點。於是,函數“method”被封裝在了接口“MyCallInterface”當中。


到了現在,可算明白了,“method”方法就是這裏的所謂的“回調函數”。

而這個例子中,整個的回調過程,就可以簡單的分解爲:

程序員A遇到難題,無法解決,於是請教程序員B = ProgrammerA call toProgrammerB

程序員B收到問題,進行思考,最終給出解決方案 = ProgrammerB call toProgrammerA

當程序員給出解決方案,通知給程序員A時,這個過程就是所謂的回調了。


當我對這個例子進行了一番琢磨後,感覺就是:

對於打電話請教問題的這個經典用例自身,是很能夠描述回調的思想的。但是,對於這個例子來說,

對於回調的說明似乎並不能得到給人一種醍醐灌頂的體驗。


至少我是這樣覺得的,所謂回調回調的,通常第一感覺就是:

既然要回,那麼就如同打電話請教問題的例子一樣,

我打電話給你了,你得回電給我,這纔算是完成了一個回調嘛。

但在上面他的例子中,這個“回電”的過程沒有一個很明顯的體現。


另一種場景似乎更能更加明顯直接的對於回調進行說明。

在其它的博客裏看了對於回調的經典應用場景有這樣一種說法:

  • Class A實現接口CallBack callback——背景1
  • class A中包含一個class B的引用b ——背景2
  • class B有一個參數爲callback的方法f(CallBack callback) ——背景3
  • A的對象a調用B的方法 f(CallBack callback) ——A類調用B類的某個方法 C
  • 然後b就可以在f(CallBack callback)方法中調用A的方法 ——B類調用A類的某個方法D

對於這種應用場景,我們來結合一些“身邊的實際現象”,就能很好的幫助自身進行理解。

最近恰逢年初,又是一年跳槽的高峯期了。

所以對於員工離職與跳槽的現象,其實也可以幫助我們對“回調”進行理解。


跳槽的很常見的原因自然就是薪資問題,那麼:

假設我們作爲一個員工,又是新的一年了,工資依舊可憐巴巴,心裏捉急啊。

這時怎麼辦呢?跳吧?那萬一剛跳,薪資又要調整了呢?

於是,決定了,先和BOSS“談判”,根據結果再做最後的決定。

這個時候,實際上也就是所謂的一種回調。

我們以一個程序猿的角度來分解這個問題:


首先,員工想要從老闆處得知關於薪資調整的結果,那麼就需要一個“途徑”,於是出現了一個回調接口:

/**
 * 回調接口
 * @author hql 
 * 2015/03/03
 */
public interface Negotiation {

	/*
	 * 實際這就是你向BOSS提供的回覆你調薪詢問的一種途徑
	 *  BOSS根據該途徑給你一個結果,而你就可以通過該結果來做出決定
	 */
	public void setNewSalary(int salary);

}

接下來,就是員工類的定義:
/**
 * 員工類
 * @author hql 
 * 2015/03/03
 */
public class Employee implements Negotiation {//背景1
	private Boss boss = new Boss(); //背景2
	private int newSalary;

	/*
	 * 開啓談判
	 */
	void startNegotiation() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				//背景:Class A調用Class B中的某個方法C
				boss.doNegotiation(Employee.this);
			}
		}).start();
	}

	/*
	 * 回調函數,從BOSS處獲取薪資調整結果
	 */
	@Override
	public void setNewSalary(int salary) {
		this.newSalary = salary;
		makeDecision();

	}

	/*
	 * 根據薪資調整結果做出最終決定
	 */
	void makeDecision() {
		if (newSalary >= 10000) {
			System.out.println("調薪滿意,繼續奮鬥!");
		} else {
			System.out.println("呵呵,拜拜了您!");
		}
	}
}

緊接着,是老闆類:
/**
 * 老闆類
 * @author hql 
 * 2015/03/03
 */
public class Boss {

	public void doNegotiation(Negotiation negotiation) {//背景3
		System.out.println("小子去年乾的不錯,漲!");
		// Boss call back to Employee - 回調
		negotiation.setNewSalary(10000);
	}

}

一切準備工作就緒,接下來就是真正的“談判了”:
/**
 * 回調函數測試類
 * @author hql 
 * 2015/03/03
 */
public class TestCallBackFunc {
	public static void main(String[] args) {
		Employee xiaoli = new Employee();
		xiaoli.startNegotiation();
	}
}

到了這裏,終於對所謂的“回調機制”有了一個較爲清晰的理解了。
正如上面的例子當中,所謂回調的過程就是指:

首先,是員工類(Class A)當中調用了老闆類(Class B)的方法:“doNegotiation”,由此向老闆提出調薪的要求。(employee call to boss)

然後,老闆收到請求,做出決定。於是老闆類(Class B)回過頭又調用了員工類(Class A)當中的方法:“setNewSalary”。(boss call to employee)


所以,其實針對回調,如果我們作更通俗的理解,其實就是:

一個員工想要調薪,針對於調薪,就自然會有一個結果,但是這個結果步是員工自身能決定的,需要由BOSS來拍板。

那麼,自然的,針對於該調薪結果,就需要一個“介質”,也可以說是一個“渠道”來在員工與BOSS之間進行溝通。

而“回調函數”在整個回調過程中,起到的作用就是這個“介質”。


我們不妨把上面的例子當中的回調函數“setNewSalary”視作一張“員工薪資調整申請表”,那麼整個回調過程就變成了:

某員工想要申請薪資調整,於是找到老闆(調用BOSS類當中的方法),告訴BOSS,我想要調薪了。

老闆收到了請求,做出了決定。現在要將這個結果返回該員工,這個返回的過程就被叫做回調。

但這個返回結果的方式肯定應該符合一定的規範,因爲至少要讓該員工意識到,BOSS已經針對於你的要求給出結果了。

“員工薪資調整申請表”就是該公司針對於“調薪結果”的提出的規範,員工根據該申請表就能得知自己的調薪結果。

所以說,之所以我們在程序中定義回調接口,其實就是在聲明一種規範,確保程序的嚴謹性。


試想一下,該老闆給出結果過後,正好碰到某個保潔人員,於是對這個清潔人員,正好,你去告訴某某員工他的調薪結果是xxxxx,那合理嗎?


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章