回调在维基百科中定义为:

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

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

举个例子可能更明白一些:以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++调用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()与hashCode()方法详解

    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()方法详解

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

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

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

随机推荐

  1. 采用EntityFramework.Extended 对EF进行扩展(Entity Framework 延伸系列2)

    前言 Entity Framework 延伸系列目录 今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这 ...

  2. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  3. Ubuntu 16.10 安装KolourPaint 4画图工具

    KolourPaint 4画图工具简单实用,可以绘画.视频处理和图标编辑: • 绘画:绘制图表和“手绘” • 视频处理:编辑截图和照片;应用特效 • 图标编辑:绘画剪贴和标识透明化 1.在Ubuntu ...

  4. Gradle 实现 Android 多渠道定制化打包

    Gradle 实现 Android 多渠道定制化打包 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在项目中遇到需要实现 Apk 多渠道.定制化打包, Google .百度查找了一些资料, ...

  5. Win10 UWP开发系列——开源控件库:UWPCommunityToolkit

    在开发应用的过程中,不可避免的会使用第三方类库.之前用过一个WinRTXamlToolkit.UWP,现在微软官方发布了一个新的开源控件库—— UWPCommunityToolkit 项目代码托管在G ...

  6. QQ空间动态爬虫

    作者:虚静 链接:https://zhuanlan.zhihu.com/p/24656161 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 先说明几件事: 题目的意 ...

  7. maven依赖查询地址

    http://search.maven.org/#search%7Cga%7C1%7C

  8. 【干货分享】流程DEMO-外出申请

    流程名: 外出申请  流程相关文件: 流程包.xml  流程说明: 直接导入流程包文件,即可使用本流程  表单:   流程: 图片:2.png DEMO包下载: http://files.cnblog ...

  9. RMS Server打开或关闭日志记录

    原文: https://technet.microsoft.com/zh-cn/library/cc732758 在 Active Directory Rights Management Servic ...

  10. SQL*Plus生成html文件

    最近使用SQL*Plus命令生成html文件,遇到一些有意思的知识点,顺便记录一下,方便以后需要的时候而这些知识点又忘记而捉急.好记性不如烂笔头吗! 为什么要用SQL*Plus生成html文件?   ...