Java回调方法详解

时间:2022-09-17 19:41:24

回调在*中定义为:

在计算机程序设计中,回调函数,是指通过函数参数传递到其他代码的,某一块可执行代码的引用。

其目的是允许底层代码调用在高层定义的子程序。

举个例子可能更明白一些:以Android中用retrofit进行网络请求为例,这个是异步回调的一个例子。

在发起网络请求之后,app可以继续其他事情,网络请求的结果一般是通过onResponseonFailure这两个方法返回得到。看一下相关部分的代码:

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类是高层的代码。

所以通过上面的解释,我们可以推断出一些东西,为了表示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();
}
}

我们通过一个测试类:

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

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

Java回调方法详解的更多相关文章

  1. JAVA本地方法详解,什么是JAVA本地方法?

    一. 什么是Native Method   简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非j ...

  2. java回调函数详解

    声明:博客参考于https://www.cnblogs.com/yangmin86/p/7090882.html,谢谢哥们 回调函数:是指在A类执行代码时,调用了B类中的方法,但B类中的方法执行了A类 ...

  3. java的方法详解和总结

    一.什么是方法 在日常生活中,我们所说的方法就是为了解决某件事情,而采取的解决办法 java中的方法可以理解为语句的集合,用来完成解决某件事情或实现某个功能的办法 方法的优点: 程序变得更加简短而清晰 ...

  4. C&plus;&plus;调用JAVA方法详解

    C++调用JAVA方法详解          博客分类: 本文主要参考http://tech.ccidnet.com/art/1081/20050413/237901_1.html 上的文章. C++ ...

  5. JAVA 注解的几大作用及使用方法详解

    JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...

  6. Java提高篇——equals&lpar;&rpar;与hashCode&lpar;&rpar;方法详解

    java.lang.Object类中有两个非常重要的方法: 1 2 public boolean equals(Object obj) public int hashCode() Object类是类继 ...

  7. Java构造和解析Json数据的两种方法详解二

    在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面接着介绍用org.json构造和解析Jso ...

  8. Java中的main&lpar;&rpar;方法详解

    在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main,方法必须是 ...

  9. 使用Java操作文本文件的方法详解

    使用Java操作文本文件的方法详解 摘要: 最初java是不支持对文本文件的处理的,为了弥补这个缺憾而引入了Reader和Writer两个类 最初java是不支持对文本文件的处理的,为了弥补这个缺憾而 ...

随机推荐

  1. 打电话&comma;发短信&comma;发邮件&comma;app跳转

    1.打电话 - (IBAction)callPhone1:(id)sender { NSURL *url = [NSURL URLWithString:@"tel://18500441739 ...

  2. jsgen 搭建

    1,mongodb 2,redis  http://www.cnblogs.com/lxx/archive/2013/06/04/3116985.html http://blog.csdn.net/w ...

  3. 玩转Docker镜像

    镜像是Docker最核心的技术之一,也是应用发布的标准格式.无论你是用docker pull image,或者是在Dockerfile里面写FROM image,从Docker官方Registry下载 ...

  4. oracle服务介绍

    按照windows 7 64位 安装oracle 11g R2中的方法成功安装Oracle 11g后,共有7个服务,这七个服务的含义分别为: 1. Oracle ORCL VSS Writer Ser ...

  5. BZOJ 4816 数字表格

    首先是惯例的吐槽.SDOI题目名称是一个循环,题目内容也是一个循环,基本上过几年就把之前的题目换成另一个名字出出来,喜大普奔亦可赛艇.学长说考SDOI可以考出联赛分数,%%%. 下面放解题报告.并不喜 ...

  6. 【尺取法】Jurisdiction Disenchantment

    [尺取法]Jurisdiction Disenchantment PROBLEM 时间限制: 1 Sec 内存限制: 128 MB 题目描述 The Super League of Paragons ...

  7. JEECG 上传插件升级-Online

    前言: 现有的uploadify上传是基于swf的,随着H5的普及,flash即将退出历史舞台,JEECG本着与时俱进的原则,将全面升级JEECG系统中的上传功能,采用新式上传插件plupload,此 ...

  8. rabbitmq使用报错总结

    最近公司重构服务器架构,需要用到rabbitmq,在公司搞了一个下午还是连接不上,后来细看了英文说明,测试连接成功,得出如下报错几点. 我用的安装包:otp_win64_21.3.exe(erlang ...

  9. IntelliJ IDEA 2018&period;2&period;2及以下版本破解方法

    破解文件下载地址:https://pan.baidu.com/s/1FKeGekyIHFUWaWi6tk2eEw =========================================== ...

  10. springmvc编码问题

    web.xml中加入 <filter> <filter-name>encodingFilter</filter-name> <filter-class> ...