首先,要明确一个点js中的回调和java中的回调其实是两个概念,可以说基本没啥关系;这里强调了这个,就是因为我之前是这么理解的,因此一时没反应过来。
好久没写js了,这里需要明白几个特性,带着这些特性去看下面的文章,会很快理解的:
- 大多数语言是同步语言,比如Java,c,但是js是一门异步语言
背景
回调
定义:
回调 (opens new window)就是把一个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。
使用回调的场景:
在调用一个函数之后,需要在函数执行中或执行后,将执行结果或状态再传递给调用者并进行一系列后续操作时,可以使用回调机制。通常是:
- 执行某个操作需要耗时,异步执行后进行回调;
- 调用者不再关心回调函数中进行的后续操作;
- 程序需要监听函数中某个动作的完成,从而进行下一步操作
在编程语言的体现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 被调用的函数:控制台打印 a 之后,调用 fun 函数 function print(a, fun){ console.log(a); fun(); } function callback(){ console.log("调用回调函数:callback") } // 主函数 function main(){ // 调用print,传入参数a 和一个回调函数 // 回调函数可以是已经声明的函数 print("参数a", callback) // 也可以是临时实现的匿名函数 print("参数a", function(){ console.log("调用匿名回调函数") }) } |
其实,根据定义很好理解
为什么需要回调?
很简单,,因为如果我们在sendHttpRequest()方法中开启一个线 程来发起HTTP请求,服务器响应的数据是无法进行返回的。这是由于所有的耗时逻辑都是在子 线程里进行的,sendHttpRequest()方法会在服务器还没来得及响应的时候就执行结束了, 当然也就无法返回响应的数据了。
上面是我的书籍笔记中对回调的一个定义,后面才发现问题所在,这其实是js中回调的定义,具体为什么这么说,看下面代码就明白了
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void printA() throws InterruptedException { Thread.sleep(1000); System.out.println("a"); } public void printB(){ System.out.println("b"); } @Test public void test4() throws InterruptedException { printA(); printB(); } |
这段代码的输出值则是,a,b。但是放到js中,这里输出的就是b,a了。这就是因为js是异步编程语言的结果造成的。后面我分析了js中的回调和java中的回调,这里其实还有一个触发点,就是我在看安卓代码的时候才发现了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//这段代码是在android的关机流程中的一个调用 mWindowManagerFuncs.shutdown(false /* confirm */); //frameworks\base\core\java\android\view\WindowManagerPolicy.java //接口 public interface WindowManagerFuncs { public void shutdown(boolean confirm); } //frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java // Called by window manager policy. Not exposed externally. @Override public void shutdown(boolean confirm) { ShutdownThread.shutdown(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, confirm); } |
这里很明显会有一个这样的声明:WindowManagerFuncs mWindowManagerFuncs = new WindowManagerService()
,这里就是java中的向上转型。回到正题,问题是什么问题?
- 这里用到了java中的回调机制,那么是如何用到的,怎么体现出来的
- 为什么代码要这么设计,或者说为什么需要这个接口WindowManagerFuncs,关于接口的功能,我之前总结过,那么这里是抽象出功能么?还是和回调有关,还是一个总结?
这两个问题,我会放到最后再说
向上转型
一句话总结:向上转型,JAVA中的一种调用方式。向上转型是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B“重写”A的方法。转型参考文档
js中的回调
基于以上背景,js中的回调就很简单明了,但是我这里还是想做一个更深入的总结:参考文档
想想还是算了,这个文档写的非常清楚,我这里就不做重复的啰嗦。
总结:一些耗时代码之所以不放在被调用的函数里,其实就是为了解耦操作。为了克服js中的异步语言机制,而让函数作为形参放在另一个函数的参数中这是主要原因,其二则是为了解耦。
java中的回调
正片来了:
方法回调:是将功能定义与功能分开的一种手段、一种解耦合的设计思想。在java中回调时通过函数接口来实现的。其本质是将实现接口的类通过向上转型至接口类型,通过传入不同的的子类,实现调用相应的子类重写的父类接口方法。
这里用一个例子进行说明:
1 2 3 4 5 6 7 |
/** * 回调的方法 * */ public interface CallBack { void result(String result); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 领导发布任务、获得结果 * */ public class Manager implements CallBack{ @Override public void result(String result) { System.out.println(result); } public void fabuTask(EmployeeInterfce employee) throws InterruptedException { System.out.println("领导发布任务"); employee.doSomthing(new Manager()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 员工做事、通知领导 * */ public class Employee implements EmployeeInterfce{ @Override public void doSomthing(CallBack callBack) { String result = "做完了"; callBack.result(result); } } |
1 2 3 4 5 6 7 8 9 10 |
public class Test { public static void main(String[] args) throws InterruptedException { Employee employee = new Employee(); Manager manager = new Manager(); manager.fabuTask(employee); System.out.println("领导继续做事"); } } |
最后的结果:
1 2 3 |
领导发布任务 做完了 领导继续做事 |
这里的结果可以看出,领导这边其实是同步调用,也就是说领导这边收到反馈后的结果后才能继续去做别的事情。这里想要改成异步调用其实也很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 领导发布任务、获得结果 * */ public class Manager implements CallBack{ @Override public void result(String result) { System.out.println(result); } public void fabuTask(EmployeeInterfce employee) throws InterruptedException { System.out.println("领导发布任务"); new Thread(new Runnable() { @Override public void run() { employee.doSomthing(new Manager()); } }).start(); } } |
可以看到改成异步最关键的就是新开一个线程即可,这个线程去等待员工的返回结果,主线程继续做自己的事情。
于是,打印出来的结果则是:
1 2 3 |
领导发布任务 领导继续做事 做完了 |
这种情况下则是异步调用。
这里给出一个更加形象的例子:回调机制之文件下载例子
总结一下:
思想:
接口回调的意义是通过接口来实现解耦的的前提下调用另一个类的方法,也就是B为A准备一个callback回调方法,然后B把任务丢给A, A做完任务然后通过调用B的方法callback。传统情况下,B要调用A的方法,那么B和A就应该是组合关系或者聚合是组合关系,但这样耦合度就确实很高,如何解耦呢?创建一个 函数型接口Task里面只有一个抽象方法就是callback,然后将B作为Task接口实现类重写callback后,将task引用作为参数来完成解耦。
实现:
- class A 实现接口 CallBack——背景 1
- class A 中包含一个 class B 的引用 b——背景 2
- class B 有一个参数为 callback 的方法 b(CallBack callback)——背景 3
- A 的对象 a 在自己的 a() 里调用 B 的方法 b(CallBack callback)——A 类调用 B 类的某个方法
- 然后 b 就可以在 b(CallBack callback) 中调用 A 的方法——B 类调用 A 类的某个方法
综上:
- 类 A 的 a() 调用类 B 的 b()。
- 类 B 的 b() 执行完毕主动调用类 A 的 callback()。
结合上面的例子帮助理解:首先领导让员工做一件事,这件事有两个关键点:1.什么时候做完是员工反馈的。2.以什么形式反馈的是领导定义的。那么这个事情,在java中要如何实现?这里需要一个接口,什么时候做完是员工反馈的,员工只需要调用这个方法即可,但是以什么形式反馈是领导定义的,因此领导类必须实现这个接口,以达到约束的效果。
总结
课外
接口回调非常类似上转型对象调用子类重写的方法
这句话要如何理解?