深入理解Java中的回撥機制(最通俗易懂的回撥機制的部落格)
阿新 • • 發佈:2019-02-18
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中對應相應的實現機制。