首先,什么是回调函数呢?
回调函数,维基百科上的定义是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这段定义看起来很容易让人犯迷糊。
举个例子来说,
有两个函数a,b和主函数c,
主函数c在运行过程中将b的引用传递给了a,这个动作就叫做在a中登记回调函数b。
然后主函数c继续运行,等到a运行(主函数调用或者事件触发)的时候,a调用登记在其中的b,这个动作叫做调用回调函数。
于是函数b开始运行。需要注意的一点是,因为b是由a调用的,所以函数b和函数a运行在同一个线程上。
关于回调函数,知乎上有一段比喻很是生动形象:
(作者:常溪玲 链接:https://www.zhihu.com/question/19801131/answer/13005983)
现在我们知道了什么是回调函数,那么我们为什么要使用回调函数呢?
笔者大概总结了一下,使用回调函数的应用场景主要有三个。
场景1、函数a属于不可变的代码,它需要你给它提供实现方式函数b,所以这里就需要你将b的引用传递给a。
不可变代码大致有两种:
一是系统的库函数,编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写库;而应用编程就是利用写好的各种库来编写具某种功用的程序,也就是应用。系统程序员会给自己写的库留下一些接口,即API(application programming interface,应用编程接口),以供应用程序员使用。所以在抽象层的图示里,库位于应用的底下。当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。
二是需要将内部细节隐藏起来的函数,只对外面露出接受函数引用的接口,从而增强代码的安全性。
场景2、函数b有很多种实现形式,通过选择在a中登记不同的b来达到动态绑定的效果,使程序更加灵活,降低耦合度。
场景3、用于异步回调的时候。
举个例子,主函数c在主线程中运行,它在a中注册回调函数b。然后启动子线程,并在子线程中运行a。运行的过程中a调用b,因为函数b是在主线程中注册的,所以b会包含主线程的数据,但是它现在却运行在子线程中。这样就完成了线程间通信,由于b中包含主线程的数据,所以大部分的操作都可以在子线程的b中完成,主函数c无需等待子线程的结束,这样就完成了异步调用。
回调函数的实现。
由于笔者学习的是java语言,所以具体来说说关于回调函数在java中的实现。相对于C语言来说,java的回调函数实现稍显麻烦一些,因为C语言中有函数指针,可以直接传递函数指针,java则是通过定义接口来实现回调函数。
具体的实现步骤如下。
- class A实现接口InA
- class A中包含一个class B的引用b
- class B有一个参数为InA的方法test(InA a)
- A的对象a调用B的方法传入自己,test(a)
- 然后b就可以在test方法中调用InA的方法
<pre name="code" class="java">public class A implements InA
{
@override
public void method()
{
System.out.println("回调");
}
public static void main(String args[])
{
B b = new B();
b.set(new A());
b.call();
}
}
<span style="font-family: Arial, Helvetica, sans-serif;">public class B</span>
{
public InA a;
public void set(InA a)
{
this.a= a;
}
public void call(){
this.a.method();
}
}