1. 程式人生 > >Java回調函數的理解與實現

Java回調函數的理解與實現

final task 任務 操作 回調函數 數通 except 接口回調 RoCE

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

在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回調函數的理解與實現