Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung der Java-Callback-Methoden

Detaillierte Erläuterung der Java-Callback-Methoden

高洛峰
高洛峰Original
2017-01-24 11:46:331498Durchsuche

Callback ist in Wikipedia wie folgt definiert:

In der Computerprogrammierung bezieht sich eine Callback-Funktion auf einen Verweis auf einen bestimmten Block ausführbaren Codes, der über Funktionsparameter an anderen Code übergeben wird.

Sein Zweck besteht darin, Low-Level-Code den Aufruf von Unterprogrammen zu ermöglichen, die auf höheren Ebenen definiert sind.

Ein Beispiel könnte es klarer machen: Nehmen Sie die Verwendung von Retrofit zur Durchführung von Netzwerkanfragen in Android. Dies ist ein Beispiel für einen asynchronen Rückruf.

Nach dem Initiieren einer Netzwerkanfrage kann die App mit anderen Dingen fortfahren. Die Ergebnisse der Netzwerkanfrage werden im Allgemeinen über die Methoden onResponse und onFailure zurückgegeben. Schauen Sie sich den relevanten Teil des Codes an:

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) {
      }
    });

Ignorieren Sie die Generika in CallBack oben. Gemäß der Definition in Wikipedia kann der gesamte Code in der anonymen inneren Klasse als übergebene Funktionsparameter betrachtet werden andere Codes. Ein Verweis auf einen Block ausführbaren Codes. Die beiden Methoden onResponse und onFailure sind Callback-Methoden. Der zugrunde liegende Code ist der unveränderte Netzwerkanforderungsteil, der geschrieben wurde, und die von der höheren Ebene definierte Unterroutine ist der Rückruf. Da die spezifische Implementierung dem Benutzer überlassen bleibt, weist sie ein hohes Maß an Flexibilität auf. Das Obige hängt mit der Enqueue-Methode (Callback-Rückruf) zusammen.

Schritte der Rückrufmethode

Der oben erwähnte Rückruf ist ein sehr allgemeines Konzept, wenn er in das Programmschreiben integriert wird, kann man sagen:

Aufruf von Klasse A zu Klasse B Eine bestimmte Methode C, und dann ruft Klasse B wiederum Methode D in Klasse A auf, wobei D die Rückrufmethode ist. Klasse B ist der Low-Level-Code und Klasse A ist der High-Level-Code.

Um die Vielseitigkeit der D-Methode auszudrücken, können wir die D-Methode als Schnittstellenmethode bezeichnen Rufen Sie Klasse A Methode D auf, dann muss Klasse A diese Schnittstelle implementieren. Auf diese Weise kommt es je nach Implementierung zu Polymorphismus, wodurch die Methode flexibel wird.

Wenn Klasse A eine bestimmte Methode C in Klasse B aufrufen möchte, muss Klasse A einen Verweis auf B enthalten, andernfalls kann sie nicht aufgerufen werden. Dieser Schritt wird als Registrieren der Callback-Schnittstelle bezeichnet. Wie implementieren Sie also Klasse B, um wiederum Methode D in Klasse A aufzurufen? Direkt über die obige Methode C. Methode C in Klasse B akzeptiert einen Parameter eines Schnittstellentyps, sodass Sie diesen Schnittstellentyp nur in Methode C verwenden müssen. Um Methode D mit den Parametern aufzurufen, können Sie wiederum Methode D in Klasse A in Klasse B aufrufen. Dieser Schritt wird als Aufruf der Callback-Schnittstelle bezeichnet.

Dies bedeutet auch, dass Methode C in Klasse B wiederum Methode D in Klasse A aufrufen muss, was einen Rückruf darstellt. Ein Aufruf von B ist ein direkter Aufruf, der als High-Level-Code unter Verwendung der zugrunde liegenden API angesehen werden kann. Wir schreiben Programme oft auf diese Weise. B, der A aufruft, ist ein Rückruf, und die zugrunde liegende API erfordert zur Ausführung High-Level-Code.

Abschließend noch eine Zusammenfassung der Schritte der Callback-Methode:

Klasse A implementiert die Schnittstelle CallBack-Callback

Klasse A enthält einen Verweis auf B

B Es gibt eine Methode f(CallBack-Rückruf) mit einem Parameter von CallBack

Rufen Sie die Methode f(CallBack-Rückruf) von B in Klasse A auf – registrieren Sie die Rückrufschnittstelle

B kann sie in f aufrufen (CallBack Die Methode zum Aufrufen von A im Callback) Methode – Aufrufen der Callback-Schnittstelle

Beispiel für einen Callback

Nehmen wir einen Sohn, der ein Spiel spielt und darauf wartet, dass seine Mutter das Essen zubereitet, bevor er benachrichtigt wird Befolgen Sie die obigen Schritte, um einen Rückruf zu schreiben. Im obigen Beispiel ist es offensichtlich, dass der Sohn die Rückrufschnittstelle implementieren sollte und die Mutter die Rückrufschnittstelle aufrufen sollte. Also definieren wir zuerst eine Rückrufschnittstelle und lassen dann unseren Sohn diese Rückrufschnittstelle implementieren.

Der Code lautet wie folgt:

public interface CallBack {
  void eat();
}
Dann müssen wir auch eine Mutterklasse definieren, die eine Methode doCook mit Schnittstellenparametern hat
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("玩游戏了中......");
  }
}

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();
  }
}

Wir bestehen eine Testklasse:

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

Dieses Beispiel ist ein typisches Rückrufbeispiel. Die Son-Klasse implementiert die Callback-Methode der Schnittstelle. Sie ruft doCook in der Mom-Klasse über die askMom-Methode auf, um die Registrierungs-Callback-Schnittstelle zu implementieren, die dem Code C in Klasse A entspricht, der Klasse B aufruft. Der doCook in der Mom-Klasse ruft den eat in der Son-Klasse zurück, um der Son-Klasse das Ergebnis mitzuteilen.

Auf diese Weise haben wir einen einfachen Rückruf implementiert, der der Definition entspricht.

Weitere Untersuchung von Rückrufbeispielen

Schauen wir uns hauptsächlich den Code der Son-Klasse an:

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("好了,我来吃饭了");
  }
}

In Der wirklich nützliche Teil dieser Klasse ist neben der Ausgabe einiger Anweisungen mom.doCook(Son.this) und das Überschreiben der eat-Methode. Daher können wir diesen Rückruf in Form einer anonymen inneren Klasse abkürzen. Der Code lautet wie folgt:

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

Brechen Sie die Son-Klasse ab und implementieren Sie die Eat-Methode direkt über die anonyme innere Klasse in der Hauptmethode. Tatsächlich sind anonyme innere Klassen die Verkörperung von Rückrufen.

Asynchroner Rückruf und synchroner Rückruf

Rückruf Was wir oben gesagt haben, ist, dass A Methode C in Klasse B aufruft und dann Methode D in Klasse A über das Objekt der Klasse A in Methode C aufruft. .

Wir sprechen zunächst über das Konzept der Synchronisation

Synchronisation

Synchronisation bezieht sich auf den Aufruf einer Methode, wenn dies beim vorherigen Methodenaufruf nicht der Fall war abgeschlossen ist, ist es nicht möglich, neue Methodenaufrufe durchzuführen. Das heißt, die Dinge müssen einzeln erledigt werden. Erst nachdem Sie die vorherige erledigt haben, können Sie die nächste erledigen.

Asynchron

Im Vergleich zur Synchronisation muss bei Asynchronität nicht auf das Ende des vorherigen Methodenaufrufs gewartet werden, bevor eine neue Methode aufgerufen wird. Daher ist bei einem asynchronen Methodenaufruf eine Methode erforderlich, um den Benutzer über das Ergebnis des Methodenaufrufs zu informieren.

So implementieren Sie eine asynchrone Implementierung

在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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn