Maison  >  Article  >  Java  >  Explication détaillée des méthodes de rappel Java

Explication détaillée des méthodes de rappel Java

高洛峰
高洛峰original
2017-01-24 11:46:331550parcourir

Le rappel est défini dans Wikipédia comme :

En programmation informatique, une fonction de rappel fait référence à une référence à un certain bloc de code exécutable qui est transmis à un autre code via des paramètres de fonction.

Son but est de permettre au code de bas niveau d'appeler des sous-programmes définis à des niveaux supérieurs.

Un exemple peut être plus clair : prenons comme exemple l'utilisation de la mise à niveau pour effectuer des requêtes réseau dans Android. Il s'agit d'un exemple de rappel asynchrone.

Après avoir lancé une requête réseau, l'application peut continuer avec d'autres choses. Les résultats de la requête réseau sont généralement renvoyés via les méthodes onResponse et onFailure. Jetez un œil à la partie pertinente du code :

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

Ignorez les génériques dans CallBack ci-dessus. Selon la définition de Wikipedia, tout le code de la classe interne anonyme peut être considéré comme des paramètres de fonction transmis à. autres codes. Une référence à un bloc de code exécutable. Les deux méthodes onResponse et onFailure sont des méthodes de rappel. Le code sous-jacent est la partie de demande réseau inchangée qui a été écrite, et le sous-programme défini par le niveau supérieur est le rappel. Parce que l'implémentation spécifique est laissée à l'utilisateur, elle a un haut degré de flexibilité. Ce qui précède est lié via la méthode enqueue (Callback callback).

Étapes de la méthode de rappel

Le rappel mentionné ci-dessus est un concept très général Lorsqu'il est mis dans l'écriture du programme, on peut dire :

Appel depuis la classe A. à la classe B Une certaine méthode C, puis la classe B appelle à son tour la méthode D dans la classe A, où D est la méthode de rappel. La classe B est le code de bas niveau et la classe A est le code de haut niveau.

Ainsi, grâce à l'explication ci-dessus, nous pouvons déduire quelque chose. Afin d'exprimer la polyvalence de la méthode D, nous utilisons la forme d'une interface pour appeler la méthode D une méthode d'interface. Ensuite, si la classe B le souhaite. appelez la méthode D de la classe A, alors la classe A doit implémenter cette interface. De cette façon, selon l'implémentation, il y aura du polymorphisme, ce qui rendra la méthode flexible.

Si la classe A veut appeler une certaine méthode C dans la classe B, alors la classe A doit contenir une référence à B, sinon elle ne peut pas être appelée. Cette étape est appelée enregistrement de l'interface de rappel. Alors, comment implémenter la classe B pour appeler à son tour la méthode D dans la classe A ? Directement via la méthode C ci-dessus. La méthode C dans la classe B accepte un paramètre d'un type d'interface, vous n'avez donc besoin d'utiliser que ce type d'interface dans la méthode C. Pour appeler la méthode D avec les paramètres, vous pouvez à votre tour appeler la méthode D en classe A en classe B. Cette étape est appelée appel de l'interface de rappel.

Cela signifie également que la méthode C de la classe B doit appeler à son tour la méthode D de la classe A, ce qui est un rappel. Un appelant B est un appel direct, qui peut être considéré comme du code de haut niveau utilisant l'API sous-jacente. Nous écrivons souvent des programmes de cette façon. B appelant A est un rappel et l'API sous-jacente nécessite l'exécution d'un code de haut niveau.

Enfin, pour résumer, les étapes de la méthode de callback :

La Classe A implémente l'interface CallBack callback

La Classe A contient une référence à B

B Il existe une méthode f(CallBack callback) avec un paramètre de CallBack

Appeler la méthode f(CallBack callback) de B dans la classe A - enregistrez l'interface de rappel

B peut l'appeler dans f (CallBack La méthode d'appel de A dans le rappel) méthode - appeler l'interface de rappel

Exemple de rappel

Prenons un fils qui joue à un jeu et attend que sa mère prépare le repas avant de notifier lui pour le manger. Suivez les étapes ci-dessus pour écrire un rappel

Dans l'exemple ci-dessus, il est évident que le fils doit implémenter l'interface de rappel et que la mère doit appeler l'interface de rappel. Nous définissons donc d'abord une interface de rappel, puis laissons notre fils implémenter cette interface de rappel.

Le code est le suivant :

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("玩游戏了中......");
  }
}

Ensuite, nous devons également définir une classe mère, qui a une méthode doCook avec des paramètres d'interface

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

Nous passons une classe de test :

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

Cet exemple est un exemple de rappel typique. La classe Son implémente la méthode de rappel de l'interface. Elle appelle doCook dans la classe Mom via la méthode AskMom pour implémenter l'interface de rappel d'enregistrement, qui est équivalente au code C de la classe A qui appelle la classe B. Le doCook de la classe Mom rappelle le eat de la classe Son pour informer la classe Son du résultat.

De cette façon, nous avons implémenté un rappel simple qui répond à la définition.

Exploration plus approfondie des exemples de rappel

Regardons principalement le code de la classe 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("好了,我来吃饭了");
  }
}

Dans cette classe, en plus de produire certaines instructions, la partie vraiment utile est mom.doCook(Son.this) et remplace la méthode eat. Par conséquent, nous pouvons abréger ce rappel sous la forme d’une classe interne anonyme. Le code est le suivant :

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

Annulez la classe Son et implémentez la méthode eat directement via la classe interne anonyme dans la méthode principale. En fait, les classes internes anonymes sont l’incarnation des rappels.

Rappels asynchrones et rappels synchrones

Callbacks Comme nous l'avons dit ci-dessus, A appelle la méthode C dans la classe B, puis appelle la méthode D dans la classe A via l'objet de la classe A dans la méthode C. .

Nous parlons d'asynchrone et de synchronisé. Parlons d'abord du concept de synchronisation

Synchronisation

La synchronisation fait référence à l'appel d'une méthode, si l'appel de méthode précédent ne l'a pas fait. étant terminé, il est impossible de faire de nouveaux appels de méthode. C'est-à-dire que les choses doivent être faites une par une. Ce n'est qu'après avoir terminé la précédente que vous pourrez faire la suivante.

Asynchrone

Par rapport à la synchronisation, l'asynchrone n'a pas besoin d'attendre la fin de l'appel de méthode précédent avant d'appeler une nouvelle méthode. Par conséquent, dans un appel de méthode asynchrone, une méthode est nécessaire pour informer l’utilisateur du résultat de l’appel de méthode.

Comment implémenter une implémentation asynchrone

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

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn