前情提要
//www.greatytc.com/writer#/notebooks/52528677/notes/101039792 #js中的回调函数
在JS中定义好一个函数A,将函数A作为入参传递给另一个函数B,B可以调用A。
C,C++中允许将函数指针作为参数传递
JavaScript,Python中允许函数名作为参数传递
在JAVA中是否可以按照这个思路实现回调函数呐?JAVA的参数只能是基本数据类型和引用数据类型,不能将一个方法当作参数传递。
面向对象的思想来思考回调函数的实现时,对象 a定义了一个方法m1,对象b想要在自己定义的m2方法使用m1方法有几种方法?
1.既然只能传递基本类型和引用类型,就把方法封装在一个类中,将类作为参数传递,再调用。
public class FuncProvider {
public void m1(){
System.out.println("this is m1");
}
}
public class FuncConsumer {
public void m2(FuncProvider p){
p.m1();
System.out.println("this is m2");
}
public static void main(String[] args) {
FuncConsumer c = new FuncConsumer();
// FuncProvider p = new FuncProvider();
c.m2(new FuncProvider());
//this is m1
//this is m2
}
}
2.通过反射获取Method和Class类型,实现回调(很麻烦)
public class FuncConsumer {
public void m3(Class clazz, Method m){
try {
m.invoke(clazz.newInstance());
System.out.println("this is m3");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
FuncConsumer c = new FuncConsumer();
try {
c.m3(FuncProvider.class,FuncProvider.class.getDeclaredMethod("m1"));
//this is m1
//this is m3
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
3.1是面向具体实现编程,不方便扩展,改为接口调用。
public interface CallbackInterface { //<-定义接口
public void call();
}
public class CallBackImpl implements CallbackInterface{ //<-实现类
@Override
public void call() {
System.out.println("通知主函数:请求已响应");
}
}
public class Request {
public void send(CallbackInterface c){ //<-接口作为参数
try {
System.out.println("请求执行");
Thread.sleep(3000);
System.out.println("请求执行完毕");
c.call();//<-执行回调函数
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
System.out.println("主函数开始");
Request request = new Request();
new Thread(
() -> {request.send(new CallBackImpl());}
).start();
System.out.println("主函数做其他事情");
}
}
模仿迅雷使用下载器下载文件,当文件下载完毕,迅雷会收到下载器通知
分析:想要使用回调函数实现迅雷收到通知,就是下载器调用迅雷提供的回调函数,迅雷就是函数提供方,下载器就是调用方。
public class Thunder {
public static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
public void downloadFile(String fileName){
executor.execute(()->new Downloader(this).doDownload(fileName));
}
protected void successCall(String fileName,int hashCode){//定义好的成功回调
//System.out.println(this.hashCode() == hashCode);//true false!!
System.out.println("thunder 收到通知:" + fileName+"下载完毕!");
}
protected void failCall(String fileName){ //定义好的失败回调
System.out.println("thunder 收到通知:" + fileName+"下载失败!");
}
}
public class Downloader {
Thunder thunder;//聚合
public Downloader(Thunder t){
this.thunder = t;
}
public void doDownload(String fileName){
System.out.println("下载器开始工作");
boolean flag = dealFile(fileName);
if(!flag){ //判断是否有资源
System.out.println("没有该资源");
this.thunder.failCall(fileName);
}else {
System.out.println(fileName + "已被下载完成");
this.thunder.successCall(fileName,this.thunder.hashCode());
}
}
boolean dealFile(String fileName){//模拟处理耗时
long mills = 0;
switch (fileName){
case "a.avi":
mills = 3000;
break;
case "b.mp3":
mills = 5000;
break;
default:
break;
}
if(mills == 0){
return false;
}
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
public class Test {
public static void main(String[] args) {//可以结合单例模式改造,不加重负担
Thunder thunder = new Thunder();
thunder.downloadFile("a.avi");
thunder.downloadFile("b.mp3");
/**
* 下载器开始工作
* 下载器开始工作
* a.avi已被下载完成
* thunder 收到通知:a.avi下载完毕!
* b.mp3已被下载完成
* thunder 收到通知:b.mp3下载完毕!
*/
}
}
回调最简单的模型:回调函数调用方聚合一个函数提供方,在某时刻使用提供方的实例调用该函数。
思考:
上边的例子中,下载器只需要通知迅雷对象,实际可能会通知多种对象,照着上例是否容易实现。