1. 什么是回调?
在我看来,回调其实是一个相当具有迷惑性的名字,因为它很容易让人纠结于回调这个词语本身的含义,从而忽略了回调这种机制的本质。要理解Java中的回调概念,最好的方式不是通过实例,而是从回调概念的起源说起。
最开始接触回调时在C语言中函数指针的时候,通过将一个函数声明为这样的形式很容易就可以实现回调:
typedef void (*PTRFUN) (int);
//自定义了一个函数实现
void eat(int food){
// do something
}
void sleep(int second){
//do something
}
//回调的接口
int CALLBACK(int choice,PTRFUN ptrFun)
{
//根据不同的函数指针,调用不同的函数
(*ptrFun)(a);//callback函数test
}
int main()
{
//这里
int eat=1,sleep=2;
int food=3,time=5;
int choice=0;
scanf("%d",&choice);
//根据不同的选择,来回调不同的函数
switch(choice){
case eat:
CALLBACK(food,&eat);//回调eat函数
break;
case sleep:
CALLBACK(time,&sleep);//回调sleep函数
break;
}
return 0;
}
当然这个例子比较简单,只是为了说明回调这种机制在C语言中的表现形式。我们从这个例子可以看出回调具有这样的特点:利用统一的接口实现了不同机制。
2. Java中的回调机制
(1)Java中回调机制的实现
很显然Java中没有函数指针的概念,回调机制的实现也就并不像C和C++语言中那样容易。随然函数指针的方式易于理解,但是指针往往也是造成程序问题的关键原因。Java中解决此问题的办法是利用接口的方式,下面我们利用Java来重写上面的例子。
package A;
//这里的接口类似于上面的函数指针
public interface FunPtr{
void callback(int choice);
}
//类似于上面的CALLBACK函数接口
public class Test{
static final int eat=1;
static final int sleep=2;
FunPtr funptr;
//这里相当于之前CALLBACK函数中修改指针形参的功能
static void setCallBack(FunPtr funptr){
this.funptr = funptr;
}
public static void main(String[] args){
int food=0,time=0;
Scanner in=new Scanner(System.in);
int choice=in.nextInt();
switch(choice){
case eat:
//这里相当于CALLBACK函数中根据不同函数指针进行函数调用
setCallback(new Eat()); //回调Eatcallback函数
funptr.callback(choice);
break;
case sleep:
setCallback(new Sleep());//回调Sleep中的callback函数
funcptr.callback(choice);
break;
}
}
}
package A;
//这里实现了同一个接口的类相当于前面的前面的具有相同形参的函数
class Eat implements FunPtr{
public void callback(int choice){
//do something
}
}
class Sleep implements FunPtr{
public void callback(int choice){
//do something
}
}
(2)Java中回调机制和C/C++中的区别
从上面的对比中我们可以总结Java实现回调机制的两个难点:
- 如何实现C/C++中函数指针的功能?Java中利用接口实现类似于函数指针的机制。
- Java实现回调机制的第二个难点在于如何实现C/C++ 中CALLBACK函数。这个函数特点是要求提供一个函数指针形参,并能够根据不同的函数形参调用不同函数。解决方法是在一个类中定义一个抽象接口的引用,然后定义一个set函数修改引用(两者组合实现相当于函数指针形参的功能)。
3.什么时候可以使用回调机制
当你需要用一个统一的接口实现不同的功能的时候,这时候回调机制就会派上用场,我们通过一个Android中广泛使用的回调的机制的例子来体会一下回调机制的使用。
(1)接口相当于函数指针
public interface OnClickListener {
void onClick(View v);
}
(2)类似于C/C++中的Callback函数
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
// 接口引用
protected OnClickListener mOnClickListener;
// 实现引用的修改
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
public boolean performClick() {
if (mOnClickListener != null) {
//根据不同的l实现不同的点击事件
mOnClickListener.onClick(this);
return true;
}
return false;
}
}
(3)两种实现方法
//这里相当于根据不同的View来实现不同的点击效果,因为Activity1实现了接口,所有View.setOnClickListener(this); 的监听事件相同
public class Activity1 extends Activity implements OnClickListener{
private Button button1;
private Button button2;
private Button button3;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
// View(button1)的监听事件
button1.setOnClickListener(this);
button1 = (Button)findViewById(R.id.button2);
// View(button2)的监听事件
button2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// 回调函数
}
}
//这里相当于根据不同的View来实现不同的点击效果
public class Activity2 extends Activity {
private Button button1;
private Button button2;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
//用内部类实现不同的button设置不同的监听事件
button1.setOnClickListener(new View.onClickListener(){
@Override
public void onClick(View v) {
// 回调函数
}
};
button2 = (Button)findViewById(R.id.button2);
button2.setOnClickListener(new View.onClickListener(){
@Override
public void onClick(View v) {
// 回调函数
}
};
}
}
4.总结
通过上面的讲解,你对Java中的回调机制已经非常明白了吧。理解回调机制的关键在于不要纠结于是如何回调的,而是应该抓住回调机制的本质,利用统一的接口实现不同的功能,然后对应C/C++实现的模型在Java中对应相应的实现机制。