Javaコールバックメソッドの詳しい説明

高洛峰
リリース: 2017-01-24 11:46:33
オリジナル
1557 人が閲覧しました

コールバックは Wikipedia で次のように定義されています:

コンピューター プログラミングでは、コールバック関数は、関数パラメーターを通じて他のコードに渡される、特定の実行可能コードへの参照を指します。

その目的は、低レベルのコードが高レベルで定義されたサブルーチンを呼び出せるようにすることです。

例を見てみましょう。Android のレトロフィットを使用したネットワーク リクエストを例に挙げます。これは非同期コールバックの例です。

ネットワーク リクエストを開始した後、アプリは他の作業を続行できます。通常、ネットワーク リクエストの結果は onResponse メソッドと onFailure メソッドを通じて返されます。コードの関連部分を見てください:

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 のジェネリックスは無視してください。Wikipedia の定義によれば、匿名内部クラスのすべてのコードは、実行可能コードの特定のブロックへの参照と見なされます。他のコードに渡される関数パラメータ。 onResponse と onFailure の 2 つのメソッドはコールバック メソッドです。基本的なコードは、記述された変更されていないネットワーク リクエスト部分であり、上位レベルで定義されたサブルーチンはコールバックです。具体的な実装はユーザーに任されているため、高い柔軟性があります。上記は enqueue(Callback callback) メソッドを通じて関連付けられます。

コールバック メソッドの手順

上記のコールバックは、プログラム作成に当てはめると、次のように言えます。

クラス A のクラス B でメソッド C を呼び出し、次にクラス A でその逆を呼び出します。クラス B クラス A のメソッド D を呼び出します。D はコールバック メソッドです。クラス B は低レベル コード、クラス A は高レベル コードです。

つまり、上記の説明から、メソッド 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 (コールバック) を順番に呼び出す必要があることも意味します。 B の呼び出しは直接呼び出しであり、基盤となる API を使用する高レベルのコードとして見ることができます。私たちはこの方法でプログラムを作成することがよくあります。 B が A を呼び出すことはコールバックであり、基礎となる API を実行するには高レベルのコードが必要です。

最後に、コールバック メソッドの手順を要約します:

クラス A は CallBack コールバック インターフェイスを実装します

クラス A には B への参照が含まれます

B には CallBack のパラメーターを持つメソッド f (CallBack コールバック) があります

クラスAのBのメソッドf(CallBackコールバック)を呼び出す - コールバックインターフェースを登録します

Bはf(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();
  }
}
ログイン後にコピー

を持つマザークラスを定義する必要もあります

テストクラスを渡します:

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 を呼び出し、登録コールバック インターフェイスを実装します。これは、クラス B を呼び出すクラス A のコード 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 クラスをキャンセルし、main メソッドの匿名内部クラスを通じて直接 Eat メソッドを実装します。実際、匿名内部クラスはコールバックの具体化です。

非同期コールバックと同期コールバック

上で説明したように、A はクラス B のメソッド C を呼び出し、次にメソッド C のクラス A のオブジェクトを通じてクラス A のメソッド D を呼び出します。

非同期と同期について話しましょう まず同期の概念について話しましょう

同期

同期とは、メソッドを呼び出すときに、前のメソッド呼び出しが実行されていない場合は、新しいメソッド呼び出しを行うことができないことを意味します。つまり、物事は 1 つずつ実行する必要があります。前の作業が完了したら、次の作業を行うことができます。

非同期

同期と比較すると、非同期では、新しいメソッドを呼び出す前に、前のメソッド呼び出しが終了するのを待つ必要がありません。したがって、非同期メソッド呼び出しでは、メソッド呼び出しの結果をユーザーに通知するメソッドが必要になります。

非同期実装を実現する方法

在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类就可以得到数据的。我们可以考虑回调。

步骤如下:

在拥有数据的那个类里面写一个回调的接口。-->这里就是B类中写一个回调接口

回调方法接收一个参数,这个参数就是要得到的数据

同样是在这个类里写一个注册回调的方法。

在注册回调方法,用接口的引用去调用回调接口,把B类的数据当做参数传入回调的方法中。

在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
ログイン後にコピー

  

以上就是通过回调的方式进行通信。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持PHP中文网!

更多Java回调方法详解相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート