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=,sleep=;
int food=,time=;
int choice=;
scanf("%d",&choice);
//根據不同的選擇,來回調不同的函數
switch(choice){
case eat:
CALLBACK(food,&eat);//回調eat函數
break;
case sleep:
CALLBACK(time,&sleep);//回調sleep函數
break;
}
return ;
}
當然這個例子比較簡單,隻是為了說明回調這種機制在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=;
static final int sleep=;
FunPtr funptr;
//這裡相當于之前CALLBACK函數中修改指針形參的功能
static void setCallBack(FunPtr funptr){
this.funptr = funptr;
}
public static void main(String[] args){
int food=,time=;
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中對應相應的實作機制。