Java回調方法詳解

回調在維基百科中定義爲:html

在計算機程序設計中,回調函數,是指經過函數參數傳遞到其餘代碼的,某一塊可執行代碼的引用。網絡

其目的是容許底層代碼調用在高層定義的子程序。
舉個例子可能更明白一些:以Android中用retrofit進行網絡請求爲例,這個是異步回調的一個例子。
在發起網絡請求以後,app能夠繼續其餘事情,網絡請求的結果通常是經過onResponseonFailure這兩個方法返回獲得。看一下相關部分的代碼:app

call.enqueue(new Callback<HistoryBean>() {
            @Override
            public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) {
                HistoryBean hb = response.body();
                if(hb == null) return;
                showText.append(hb.isError() + "");
                for(HistoryBean.ResultsBean rb : hb.getResults()){
                    showText.append(rb.getTitle() + "/n");
                }
            }

            @Override
            public void onFailure(Call<HistoryBean> call, Throwable t) {

            }
        });

忽略上面CallBack中的泛型,按照維基百科中的定義,匿名內部類裏面的所有代碼能夠當作函數參數傳遞到其餘代碼的,某一塊可執行代碼的引用。 onResponseonFailure這兩個方法就是回調方法。底層的代碼就是已經寫好不變的網絡請求部分,高層定義的子程序就是回調,由於具體的實現交給了使用者,因此具有了很高的靈活性。上面就是經過enqueue(Callback callback)這個方法來關聯起來的。框架

回調方法的步驟

上面說的回調是很通用的概念,放到程序書寫上面,就能夠說:異步

A類中調用B類中的某個方法C,而後B類中在反過來調用A類中的方法D,在這裏面D就是回調方法。B類就是底層的代碼,A類是高層的代碼。ide

因此經過上面的解釋,咱們能夠推斷出一些東西,爲了表示D方法的通用性,咱們採用接口的形式讓D方法稱爲一個接口方法,那麼若是B類要調用A類中的方法D,那勢必A類要實現這個接口,這樣,根據實現的不一樣,就會有多態性,使方法具有靈活性。
A類要調用B類中的某個方法C,那勢必A類中必須包含B的引用,要否則是沒法調用的,這一步稱之爲註冊回調接口。那麼如何實現B類中反過來調用A類中的方法D呢,直接經過上面的方法C,B類中的方法C是接受一個接口類型的參數,那麼只須要在C方法中,用這個接口類型的參數去調用D方法,就實現了在B類中反過來調用A類中的方法D,這一步稱之爲調用回調接口。
這也就實現了B類的C方法中,須要反過來再調用A類中的D方法,這就是回調。A調用B是直調,能夠當作高層的代碼用底層的API,咱們常常這樣寫程序。B調用A就是回調,底層API須要高層的代碼來執行。
最後,總結一下,回調方法的步驟:函數

  • A類實現接口CallBack callback
  • A類中包含了一個B的引用
  • B中有一個參數爲CallBack的方法f(CallBack callback)
  • 在A類中調用B的方法f(CallBack callback)——註冊回調接口
  • B就能夠在f(CallBack callback)方法中調用A的方法——調用回調接口

回調的例子

咱們以一個兒子在玩遊戲,等媽媽把飯作好在通知兒子來吃爲例,按照上面的步驟去寫回調;
上面的例子中,顯然應該兒子來實現回調接口,母親調用回調接口。因此咱們先定義一個回調接口,而後讓兒子去實現這個回調接口。
其代碼以下:測試

public interface CallBack {

    void eat();
}
public class Son implements CallBack{

    private Mom mom;

    //A類持有對B類的引用
    public void setMom(Mom mom){
        this.mom = mom;
    }

    @Override
    public void eat() {
        System.out.println("我來吃飯了");
    }

    public void askMom(){
        //經過B類的引用調用含有接口參數的方法。
         System.out.println("飯作了嗎?");
        System.out.println("沒作好,我玩遊戲了");
        new Thread(() -> mom.doCook(Son.this)).start();
        System.out.println("玩遊戲了中......");
    }
}

而後咱們還須要定義一個母親的類,裏面有一個含有接口參數的方法doCook優化

public class Mom {


    //在含有接口參數的方法中利用接口參數調用回調方法
    public void doCook(CallBack callBack){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("作飯中......");
                    Thread.sleep(5000);
                    callBack.eat();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

咱們經過一個測試類:this

public class Test {

    public static void main(String[] args) {
        Mom mom = new Mom();
        Son son = new Son();
        son.setMom(mom);
        son.askMom();
    }

}

這個例子就是典型的回調的例子。Son類實現了接口的回調方法,經過askMom這個方法調用Mom類中的doCook,實現註冊回調接口,至關於A類中調用B類的代碼C。在Mom類中的doCook來回調Son類中的eat,來告訴Son類中的結果。
這樣,咱們就實現了一個簡單的,符合定義的回調。

回調例子的進一步探索

咱們主要看一下Son類的代碼:

public class Son implements CallBack{

    public Mom mom;
    public Son(Mom mom){
        this.mom = mom;
    }

    public void askMom(){
        System.out.println("飯作了嗎?");
        System.out.println("沒作好,我玩遊戲了");
        new Thread(() -> mom.doCook(Son.this)).start();
        System.out.println("玩遊戲了中......");
    }
    @Override
    public void eat() {
        System.out.println("好了,我來吃飯了");
    }
}

這個類裏面,除了輸出一些語句以外,真正有用的部分是mom.doCook(Son.this)以及重寫eat方法。因此,咱們能夠經過匿名內部類的形式,簡寫這個回調。其代碼以下:

public class CallBackTest {
    public static void main(String[] args) {
        Mom mom = new Mom();
        new Thread(()-> mom.doCook(() -> System.out.println("吃飯了......"))).start();
    }
}

取消Son類,直接在主方法中經過匿名內部類去實現eat方法。其實匿名內部類就是回調的體現。

異步回調與同步回調

回調上面咱們講了 就是A調用B類中的方法C,而後在方法C裏面經過A類的對象去調用A類中的方法D。
咱們在說一下異步與同步,先說同步的概念

同步
同步指的是在調用方法的時候,若是上一個方法調用沒有執行完,是沒法進行新的方法調用。也就是說事情必須一件事情一件事情的作,作完上一件,才能作下一件。
異步
異步相對於同步,能夠不須要等上個方法調用結束,才調用新的方法。因此,在異步的方法調用中,是須要一個方法來通知使用者方法調用結果的。

實現異步的方式

在Java中最常實現的異步方式就是讓你想異步的方法在一個新線程中執行。

咱們會發現一點,異步方法調用中須要一個方法來通知使用者調用結果,結合上面所講,咱們會發現回調方法就適合作這個事情,經過回調方法來通知使用者調用的結果。
那異步回調就是A調用B的方法C時是在一個新線程當中去作的。
上面的母親通知兒子吃飯的例子,就是一個異步回調的例子。在一個新線程中,調用doCook方法,最後經過eat來接受返回值,固然使用lamdba優化以後的,本質是同樣的。
同步回調就是A調用B的方法C沒有在一個新線程,在執行這個方法C的時候,咱們什麼都不能作,只能等待他執行完成。

同步回調與異步回調的例子

咱們看一個Android中的一個同步回調的例子:

button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
             Log.i("button","被點擊");
        }
});

button經過setOnClickListener註冊回調函數,和上面寫的同樣,經過匿名內部類的形式將接口的引用傳進去。因爲button調用setOnClickListener沒有新建一個線程,因此這個是同步的回調。
而異步回調,就是咱們開篇講的那個例子:

call.enqueue(new Callback<HistoryBean>() {
            @Override
            public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) {
                HistoryBean hb = response.body();
                if(hb == null) return;
                showText.append(hb.isError() + "");
                for(HistoryBean.ResultsBean rb : hb.getResults()){
                    showText.append(rb.getTitle() + "/n");
                }
            }

            @Override
            public void onFailure(Call<HistoryBean> call, Throwable t) {

            }
        });

這個enqueue方法是一個異步方法去請求遠程的網絡數據。其內部實現的時候是經過一個新線程去執行的。
經過這兩個例子,咱們能夠看出同步回調與異步回調的使用實際上是根據不一樣的需求而設計。不能說一種取代另外一種,像上面的按鈕點擊事件中,若是是異步回調,用戶點擊按鈕以後其點擊效果不是立刻出現,而用戶又不會執行其餘操做,那麼會感受很奇怪。而像網絡請求的異步回調,由於受限於請求資源可能不存在,網絡鏈接不穩定等等緣由致使用戶不清楚方法執行的時候,因此會用異步回調,發起方法調用以後去作其餘事情,而後等回調的通知。

回調方法在通訊中的應用

上面提到的回調方法,除了網絡請求框架的回調除外,其回調方法都是沒有參數,下面,咱們看一下在回調方法中加入參數來實現一些通訊問題。
若是咱們想要A類獲得B類通過一系列計算,處理後數據,並且兩個類是不能經過簡單的將B的引用給A類就能夠獲得數據的。咱們能夠考慮回調。
步驟以下:

  1. 在擁有數據的那個類裏面寫一個回調的接口。-->這裏就是B類中寫一個回調接口
  2. 回調方法接收一個參數,這個參數就是要獲得的數據
  3. 一樣是在這個類裏寫一個註冊回調的方法。
  4. 在註冊回調方法,用接口的引用去調用回調接口,把B類的數據當作參數傳入回調的方法中。
  5. 在A類中,用B類的引用去註冊回調接口,把B類中的數據經過回調傳到A類中。

上面說的步驟,有點抽象。下面咱們看一個例子,一個是Client,一個是Server。Client去請求Server通過耗時處理後的數據。

public class Client{

    public Server server;
    public String request;
    //連接Server,獲得Server引用。
    public Client connect(Server server){
        this.server = server;
        return this;
    }

    //Client,設置request
    public Client setRequest(String request){
        this.request = request;
        return this;
    }
    
    //異步發送請求的方法,lamdba表達式。
    public void enqueue(Server.CallBack callBack){
        new Thread(()->server.setCallBack(request,callBack)).start();
    }
}
public class Server {
    public String response = "這是一個html";
    //註冊回調接口的方法,把數據經過參數傳給回調接口
    public void setCallBack(String request,CallBack callBack){
        System.out.println("已經收到request,正在計算當中......");
        new Thread(() -> {
            try {
                Thread.sleep(5000);
                callBack.onResponse(request + response);
            } catch (InterruptedException e) {
                e.printStackTrace();
                callBack.onFail(e);
            }
        }).start();
    }

    //在擁有數據的那個類裏面寫一個接口
    public interface CallBack{
        void onResponse(String response);
        void onFail(Throwable throwable);
    }
}

接下來,咱們看一下測試的例子:

public class CallBackTest {

    public static void main(String[] args) {

        Client client = new Client();
        client.connect(new Server()).setRequest("這個文件是什麼?").enqueue(new Server.CallBack() {
            @Override
            public void onResponse(String response) {
                System.out.println(response);
            }

            @Override
            public void onFail(Throwable throwable) {
                System.out.println(throwable.getMessage());
            }
        });
    }
}

結果以下:

已經收到request,正在計算當中......
這個文件是什麼?這是一個html

以上就是經過回調的方式進行通訊

相關文章
相關標籤/搜索