【伺服器】Java 是如何利用介面避免函數回調的

一、引言

在許多編程語言中,都有函數回調這一概念。C 和 C++中有函數指針,因此可以將函數作為參數傳給其它函數,以便過後調用。而在 JavaScript中,更是將函數回調發揮到了極致,各種事件的處理,特別是非同步事件,基本都靠函數回調來完成。

在 Java中,同樣可以實現函數回調。雖然沒有函數指針,但 Java 可以通過反射機制來獲得一個類的方法,將其以java.lang.reflect.Method 類型參數傳遞給其它函數,然後通過 Method 對象的 invoke方法來調用該函數。

儘管如此,這種方式的調用步驟相對繁瑣、執行效率低、難以調試。在 Java中,有比函數回調更加優雅的機制,那就是介面。

Advertisements

二、為什麼需要函數回調

函數回調,實際上是延遲實現某些功能的一種方式。

如果我們事先知道程序應該執行哪些操作,那麼完全不需要函數回調,直接在編程時實現即可。

但很多時候,在編寫代碼時,特別是寫工具類、功能庫或框架時,實現的是相對通用和抽象的功能,而具體場景下的功能則由使用這些類的開發者來實現。

函數回調,可以解決這種事先不知道具體實現的情況。

排序函數的例子

舉例來說,當我們要實現一個通用的排序函數時,事先並不知道其他開發者會用該函數來對哪些類型的元素進行排序,也就不知道以何種標準來判斷這些元素的偏序(大小)關係。

因此,可以要求其他開發者在使用排序函數時,必須提供一個比較函數 compare,這樣我們就可以用 compare比較待排序元素的大小,而無需事先知道元素是什麼類型,也無需知道 compare 的具體實現。

Advertisements

這裡 compare函數對於排序函數來說,就是回調函數。

偽代碼表示如下:

非同步處理函數的例子

再比如說,當我們編寫一個非同步處理函數時,事先不知道其他開發者在處理完成時要進行哪些操作,因為這些操作只有在特定場景下使用該函數時才能知道。

於是可以要求開發者在使用該函數時,提供一個回調函數callback。這樣我們在編寫非同步處理函數時,就可以調用 callback函數來進行一些收尾的工作,而無需事先知道這些收尾的工作是什麼。

偽代碼表示如下:

三、用介面代替函數回調

上面我們提到,之所以使用函數回調這一方式,是因為事先不知道某些功能的具體實現,因此將具體實現留給其他開發者完成。

有沒有覺得這句話彷彿在描述 Java的介面?介面(interface)是一組方法的抽象定義,具體實現由實現該介面的類來完成。

所以,利用面向對象和介面這兩個特性,可以代替函數回調。

我們以上面舉的兩個例子來說明介面是如何代替函數回調的。

排序函數

用介面實現排序函數,不再要求開發者在使用該排序函數時提供回調函數compare,而是要求開發者確保待排序元素實現了 Comparable 介面,基於「待排序元素已經實現了 Comparable介面「這一前提下,我們無需知道待排序元素的類型,就可以實現排序功能。

非同步處理函數

使用介面來實現非同步處理函數時,不要求開發者提供回調函數callback,而是要求提供一個實現了指定介面的對象,這很好地體現了 Java面向對象的思想。相比提供一個函數,一個對象包含的信息更豐富,使用起來更加靈活。但本質上,該非同步處理函數還是利用介面來完成收尾工作的。


四、總結

回調方式可以總結為:實現一個通用函數func,在具體場景中調用這個通用函數時,調用者需要提供合適的回調函數 callback。通用函數 func利用該回調函數,完成具體場景中的任務。

而介面實現的方式則是:實現一個通用函數func,在具體場景中調用這個通用函數時,被操作的對象需要自己實現合適的介面,通用函數會利用該介面,完成具體場景中的任務。

利用函數回調或者介面,都可以解決事先不知道具體實現的情況。函數回調方式傳遞的是函數,而介面方式傳遞的是實現了該介面的對象。

在 Java中,函數回調需要利用反射機制來完成,易出錯、效率低,而使用介面可以讓代碼的邏輯更加清晰、運行效率更高、也更便於調試。

Advertisements

你可能會喜歡