Java回调方法详解
作者:byhieg 发布时间:2022-09-25 08:33:54
回调在 * 中定义为:
在计算机程序设计中,回调函数,是指通过函数参数传递到其他代码的,某一块可执行代码的引用。
其目的是允许底层代码调用在高层定义的子程序。
举个例子可能更明白一些:以Android中用retrofit进行网络请求为例,这个是异步回调的一个例子。
在发起网络请求之后,app可以继续其他事情,网络请求的结果一般是通过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中的泛型,按照 * 中的定义,匿名内部类里面的全部代码可以看成函数参数传递到其他代码的,某一块可执行代码的引用。 onResponse与onFailure这两个方法就是回调方法。底层的代码就是已经写好不变的网络请求部分,高层定义的子程序就是回调,因为具体的实现交给了使用者,所以具备了很高的灵活性。上面就是通过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类就可以得到数据的。我们可以考虑回调。
步骤如下:
在拥有数据的那个类里面写一个回调的接口。-->这里就是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
来源:http://www.cnblogs.com/qifengshi/p/6098777.html


猜你喜欢
- 前言本文提供三种不同的解决方式,也是三种不同的情况和思路我的问题是在springboot整合了xxl-job一段时间后出现的。如果你程序里集
- 今天工作中遇到一个需求,就是获取 excel 里面的内容,并且把 excel 另存为 csv,因为本人以前未接触过,所以下面整理出来的代码均
- 静态变量静态变量位于栈上,它是一个全局变量,在编译期就已经生成。public class Cow{public static int cou
- 感谢《Android源码设计模式解析与实战》 何红辉 关爱民 著适配器模式在我们的开发中使用率极高,从代码中随处可见的Adapter就可以判
- Java IO 转化流乱码引起转换流读取乱码读取电脑磁盘上的Java.txt文件内容,文件路径: e:\Java\Java.txt
- 二分查找又称折半查找,它是一种效率较高的查找方法。折半查找的算法思想是将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以
- 简介接下来会讲解怎么用SpringBoot整合OpenCV初始化SpringBoot项目这里正常初始一个SpringBoot项目依赖文件在安
- 推荐教程IntelliJ IDEA 2020最新激活码(亲测有效,可激活至 2089 年)最新idea2021注册码永久激活(激活到2100
- Android 解决TextView排版参差不齐的问题在app中,展示数据时,里面有汉字、数字、特殊字符时,由于全角、半角问题导
- 在你的jar文件当前目录中建立一个bat文件:内容是:注意文件名要对应@echo offSTART "commandServer&
- 本文初步讲述了C#的CLR内存原理。这里所关注的内存里面说没有寄存器的,所以我们关注的只有托管堆(heap),栈(stack), 字符串常量
- 报错org.springframework.web.util.NestedServletException: Request process
- 1、锁优化在JDK6之前,通过synchronized来实现同步效率是很低的,被synchronized包裹的代码块经过javac编译后,会
- 时间戳转换:/// <summary>/// C#时间格式转换为时间戳(互转)/// 时间戳定义为从格林威治时间 1970年01
- 一个简单的拼图小游戏,供大家参考,具体内容如下1.首先设计视图面板。2.添加所需要的图片按钮。3.最主要的是设计监听事件,添加图片的监听按钮
- 在之前的C#版本中, 如果我们想要进行异步的Udp, 需要单开线程接收消息, C#7.1开始, 我们可以使用async/await关键字来编
- 本文主要介绍了C# WinForm状态栏实时显示当前时间(窗体状态栏StatusStrip示例),分享给大家,具体如下:实现效果:通过Sta
- b/s系统中对http请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的
- 因为某些需求,要在特定的时间执行一些任务,比如定时删除服务器存储的数据缓存,定时获取数据以及定时发送推送等等,这时就需要用到定时任务了。定时
- 本文实例为大家分享了java简单实现斗地主发牌的具体代码,供大家参考,具体内容如下问题:参考斗地主的游戏规则,完成一个发牌的功能(54张牌,