Java回調函數的理解與實現
回調函數,或簡稱回調,是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計允許了底層代碼調用在高層定義的子程序。
在Java裏面,我們使用接口來實現回調。舉個例子
所謂的回調,就是程序員A寫了一段程序(程序a),其中預留有回調函數接口,並封裝好了該程序。程序員B要讓a調用自己的程序b中的一個方法,於是,他通過a中的接口回調自己b中的方法。
舉個例子:
1. 首先定義一個類Caller,按照上面的定義就是程序員A寫的程序a,這個類裏面保存一個接口引用。
public class Caller { private MyCallInterface callInterface;public Caller() { } public void setCallFunc(MyCallInterface callInterface) { this.callInterface = callInterface; } public void call() { callInterface.printName(); } }
2. 接口的定義,方便程序員B根據定義編寫程序實現接口。
public interface MyCallInterface { public void printName(); }
3. 第三是定義程序員B寫的程序b
public class Client implements MyCallInterface { @Override public void printName() { System.out.println("This is the client printName method"); } }
4. 測試
public class Test { public static void main(String[] args) { Caller caller = new Caller(); caller.setCallFunc(new Client()); caller.call(); } }
這樣我們可以看到程序a中保留有接口成員變量,使得程序a可以通過這個接口變量調用這個接口任意實現類的方法。而程序b被調用的方法就是回調函數。
接下來在看一個具體的實現:
下面使用java回調函數來實現一個測試函數運行時間的工具類:
如果我們要測試一個類的方法的執行時間,通常我們會這樣做:
public class TestObject { /** * 一個用來被測試的方法,進行了一個比較耗時的循環 */ public static void testMethod(){ for ( int i= 0 ; i< 100000000 ; i++){ } } /** * 一個簡單的測試方法執行時間的方法 */ public void testTime(){ long begin = System.currentTimeMillis(); //測試起始時間 testMethod(); //測試方法 long end = System.currentTimeMillis(); //測試結束時間 System.out.println("[use time]:" + (end - begin)); //打印使用時間 } public static void main(String[] args) { TestObject test=new TestObject(); test.testTime(); } }
下面我們來做一個函數實現相同功能但更靈活:
首先定一個回調接口:
public interface CallBack { //執行回調操作的方法 void execute(); }
然後再寫一個工具類:
public class Tools { /** * 測試函數使用時間,通過定義CallBack接口的execute方法 * @param callBack */ public void testTime(CallBack callBack) { long begin = System.currentTimeMillis(); //測試起始時間 callBack.execute(); ///進行回調操作 long end = System.currentTimeMillis(); //測試結束時間 System.out.println("[use time]:" + (end - begin)); //打印使用時間 } public static void main(String[] args) { Tools tool = new Tools(); tool.testTime(new CallBack(){ //定義execute方法 public void execute(){ //這裏可以加放一個或多個要測試運行時間的方法 TestObject.testMethod(); } }); } }
一個待測試的,較耗時的方法:
public class TestObject { /** * 一個用來被測試的方法,進行了一個比較耗時的循環 */ public static void testMethod(){ for ( int i= 0 ; i< 100000000 ; i++){ } } }
這裏我們沒有寫程序b去實現Callback接口,而是通過匿名內部類的方法來實現。同樣也實現了回調函數。
之後我們看看為什麽要使用回調函數:
所謂回調函數就是A調用了B,B在適當的時候又反回去調用A。多數時候因為是單線程,A沒有必要等B來調用它,因為A在調用完B之後完全可以調用自己需要的操作。所以回調多見於事件驅動機制裏。因為A在調用完B之後不知道B什麽時候會完成,所以A不知道B什麽時候會完成。而唯一知道B什麽時候完成的當然是B自己了,所以當B完成的時候會通過一個回調函數通知A,自己已經完成了,這時候A才知道該進行下面的操作。如果不這樣的話,A就只能不斷地詢問B是不是已經完成了(就是輪詢),可見是效率非常低的,實現也很麻煩。
回調通常是在兩個不同的線程間需要同步的情況下才出現的,但是很多時候又沒有必要用信號量去進行真正的線程同步,因為會很復雜,而且沒有必要。所以有了回調。至於回調要幹的事情,當然是你自己決定了。
說一下同步回調和異步回調:
同步指的是調用一個方法,調用方要等待該方法所執行的任務完全執行完畢,然後控制權回到調用方;異步指的是調用一個方法,調用方不等該方法執行的任務完畢就返回,當任務執行完畢時會自動執行調用方傳入的一塊代碼。
同步:
void runTask { doTask1() doTask2() }
同步調用,執行完 doTask1 在執行 doTask2
異步
doTask1(new Callback() { void call() { doTask3() } }); doTask2();
異步回調,會同時執行 doTask1 和 doTask2, 在執行完 doTask1 後執行 doTask3
同步調用適合執行耗時短的任務,異步回調適合執行耗時長的任務,而且調用它之後調用的任務沒什麽關系。
看一個異步回調的案例:
回調接口類:
/** * 回調模式-回調接口類 */ public interface CSCallBack { public void process(String status); }
模擬客戶端:
/** * 回調模式-模擬客戶端類 */ public class Client implements CSCallBack { private Server server; public Client(Server server) { this.server = server; } public void sendMsg(final String msg){ System.out.println("客戶端:發送的消息為:" + msg); new Thread(new Runnable() { @Override public void run() { server.getClientMsg(Client.this,msg); } }).start(); System.out.println("客戶端:異步發送成功"); } @Override public void process(String status) { System.out.println("客戶端:服務端回調狀態為:" + status); } }
模擬服務端:
/** * 回調模式-模擬服務端類 */ public class Server { public void getClientMsg(CSCallBack csCallBack , String msg) { System.out.println("服務端:服務端接收到客戶端發送的消息為:" + msg); // 模擬服務端需要對數據處理 try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("服務端:數據處理成功,返回成功狀態 200"); String status = "200"; csCallBack.process(status); } }
測試類:
/** * 回調模式-測試類 */ public class CallBackTest { public static void main(String[] args) { Server server = new Server(); Client client = new Client(server); client.sendMsg("Server,Hello~"); } }
Java回調函數的理解與實現