一、引言
在許多編程語言中,都有函數回調這一概念。 C 和C++ 中有函數指針,因此可以將函數作為參數傳給其它函數,以便過後調用。而在JavaScript 中,更是將函數回調發揮到了極致,各種事件的處理,特別是異步事件,基本都靠函數回調來完成。
在Java 中,同樣可以實現函數回調。雖然沒有函數指針,但Java 可以通過反射機制來獲得一個類的方法,將其以java.lang.reflect.Method 類型參數傳遞給其它函數,然後通過Method 對象的invoke 方法來調用該函數。
儘管如此,這種方式的調用步驟相對繁瑣、執行效率低、難以調試。在Java 中,有比函數回調更加優雅的機制,那就是接口。
二、為什麼需要函數回調
函數回調,實際上是延遲實現某些功能的一種方式。
如果我們事先知道程序應該執行哪些操作,那麼完全不需要函數回調,直接在編程時實現即可。
但很多時候,在編寫代碼時,特別是寫工具類、功能庫或框架時,實現的是相對通用和抽象的功能,而具體場景下的功能則由使用這些類的開發者來實現。
函數回調,可以解決這種事先不知道具體實現的情況。
排序函數的例子
舉例來說,當我們要實現一個通用的排序函數時,事先並不知道其他開發者會用該函數來對哪些類型的元素進行排序,也就不知道以何種標準來判斷這些元素的偏序(大小)關係。
因此,可以要求其他開發者在使用排序函數時,必須提供一個比較函數compare,這樣我們就可以用compare 比較待排序元素的大小,而無需事先知道元素是什麼類型,也無需知道compare 的具體實現。
這裡compare 函數對於排序函數來說,就是回調函數。
偽代碼表示如下:
//通用的排序函數void sort(Object[] array, Method compare) { //利用compare 函數比較array 中元素的大小關係//以便對array 進行排序}//由調用者實現具體的比較函數int compare(Object a, Object b) { //比較元素a、b,並返回大小關係}異步處理函數的例子
再比如說,當我們編寫一個異步處理函數時,事先不知道其他開發者在處理完成時要進行哪些操作,因為這些操作只有在特定場景下使用該函數時才能知道。
於是可以要求開發者在使用該函數時,提供一個回調函數callback。這樣我們在編寫異步處理函數時,就可以調用callback 函數來進行一些收尾的工作,而無需事先知道這些收尾的工作是什麼。
偽代碼表示如下:
//異步處理函數void asynProcess(Method callback) { //執行異步任務callback();}//由調用者實現具體的回調函數void callback() { //異步處理完成後要進行的操作}三、用接口代替函數回調
上面我們提到,之所以使用函數回調這一方式,是因為事先不知道某些功能的具體實現,因此將具體實現留給其他開發者完成。
有沒有覺得這句話彷彿在描述Java 的接口?接口(interface)是一組方法的抽象定義,具體實現由實現該接口的類來完成。
所以,利用面向對象和接口這兩個特性,可以代替函數回調。
我們以上面舉的兩個例子來說明接口是如何代替函數回調的。
排序函數
用接口實現排序函數,不再要求開發者在使用該排序函數時提供回調函數compare,而是要求開發者確保待排序元素實現了Comparable 接口,基於“待排序元素已經實現了Comparable 接口“這一前提下,我們無需知道待排序元素的類型,就可以實現排序功能。
//通用的排序函數void sort(Object[] array) { //利用Comparable 接口的compareTo 方法//比較元素的大小,以便對array 進行排序。 }//由排序函數定義的接口public interface Comparable { public int compareTo(Object other);}//由調用者實現Comparable 接口public class Element implements Comparable { @Override public int compareTo(Object other) { //判斷當前Element 與other 的大小關係//並返回兩者的關係}}異步處理函數
使用接口來實現異步處理函數時,不要求開發者提供回調函數callback,而是要求提供一個實現了指定接口的對象,這很好地體現了Java 面向對象的思想。相比提供一個函數,一個對象包含的信息更豐富,使用起來更加靈活。但本質上,該異步處理函數還是利用接口來完成收尾工作的。
//異步處理函數void asynProcess(ActionListener al) { //執行異步任務al.actionPerformed();}//由異步處理函數定義的接口public interface ActionListener { void actionPerformed();}//由調用者實現ActionListener 接口public class ExtraTask implements ActionListener { @Override public void actionPerformed() { //異步處理函數執行完成時,需要進行的額外工作}}//調用異步處理函數public static void main(String[] args) { asynProcess(new ExtraTask());}四、總結
回調方式可以總結為:實現一個通用函數func,在具體場景中調用這個通用函數時,調用者需要提供合適的回調函數callback。通用函數func 利用該回調函數,完成具體場景中的任務。
而接口實現的方式則是:實現一個通用函數func,在具體場景中調用這個通用函數時,被操作的對象需要自己實現合適的接口,通用函數會利用該接口,完成具體場景中的任務。
利用函數回調或者接口,都可以解決事先不知道具體實現的情況。函數回調方式傳遞的是函數,而接口方式傳遞的是實現了該接口的對象。
在Java 中,函數回調需要利用反射機制來完成,易出錯、效率低,而使用接口可以讓代碼的邏輯更加清晰、運行效率更高、也更便於調試。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。