Java回撥深入理解
1 介面回撥
1.1 介面回撥概念
什麼是介面回撥
介面回撥
是指:可以把使用某一介面的類建立的物件的引用賦給該介面宣告的介面變數,那麼該介面變數就可以呼叫被類實現的介面的方法。實際上,當介面變數呼叫被類實現的介面中的方法時,就是通知相應的物件呼叫介面的方法,這一過程稱為物件功能的介面回撥。
interface People{ void peopleList(); } class Student implements People{ public void peopleList(){ System.out.println("I’m a student."); } } class Teacher implements People{ public void peopleList(){ System.out.println("I’m a teacher."); } } public class Example{ public static void main(String args[]){ People a; //宣告介面變數 a=new Student(); //例項化,介面變數中存放物件的引用 a.peopleList(); //介面回撥 a=new Teacher(); //例項化,介面變數中存放物件的引用 a.peopleList(); //介面回撥 } } 結果: I’m a student. I’m a teacher.
再來看看向上轉型(upcasting)的概念。
1.2 向上轉型
Shape s=new Circle();
,這裡,建立了一個Circle
物件,並把得到的引用立即賦值給Shape
。通過繼承,Circle
就是一種Shape
。
假設你呼叫基類方法(它已在匯出類中被覆蓋):
s.draw();
由於後期繫結(多型
),將會正確呼叫Circle.draw()
方法。
1.3向上轉型與介面回撥的區別
看似向上轉型和介面回撥是一回事。看下面兩句話,均出自Thinking in Java
使用介面的核心原因:為了能夠向上轉型為多個基型別。即利用介面的多實現,可向上轉型為多個介面基型別(具體見《抽象與介面》章節6)。
從實現了某介面的物件,得到對此介面的引用,與向上轉型為這個物件的基類,實質上效果是一樣的。(此句摘自Thinking in Java 3rd 介面與內部類一章)
所以,這兩個概念是從兩個方面來解釋一個行為。介面回撥的概念,強調使用介面來實現回撥物件方法使用權的功能。而向上轉型則牽涉到多型和執行期繫結的範疇。
1.4 用Java介面實現回撥函式的等價功能
熟悉 MS-Windows
和 X Window System
事件驅動程式設計模型的開發人員,習慣於傳遞在某種事件發生時呼叫(即回撥
)的函式指標。Java
的面向物件模型目前並不支援方法指標,Java
的介面支援提供了一種獲得回撥的等價功能的機制。其技巧就是:定義一個簡單介面,並在該介面中宣告我們要呼叫的方法。
假定我們希望在某個事件發生時得到通知。我們可以定義一個介面:
InterestingEvent.java
package org.zj.sample; public interface InterestingEvent { public void interestingEvent (); }
這使得我們可以控制實現該介面的類的任何物件。因此,我們不必關心任何外部型別資訊。
發出事件訊號的類必須等待實現了 InterestingEvent 介面的物件,並在適當時候呼叫 interestingEvent() 方法。
EventNotifier.java
package org.zj.sample;
public class EventNotifier {
private InterestingEvent ie;
private boolean somethingHappened;
public EventNotifier(InterestingEvent event) {
ie = event; // 儲存事件物件以備後用。
somethingHappened = false; // 還沒有要報告的事件。
}
public void doWork() {
if (somethingHappened) { // 檢查設定的謂詞。
ie.interestingEvent();// 通過呼叫介面的這個方法發出事件訊號。
}
}
public void setHappened(){//設定謂詞。
somethingHappened=true;
}
}
在上例中,使用 somethingHappened
謂詞來跟蹤是否應觸發事件。希望接收事件通知的程式碼必須實現 InterestingEvent
介面,並將自身引用傳遞給事件通知程式。
CallMe.java
package org.zj.sample;
public class CallMe implements InterestingEvent {
@SuppressWarnings("unused")
private EventNotifier en;
public CallMe() {
// 注意 EventNotifier (InterestingEvent event),應該傳遞一個介面型別。
// 而下面將this,即實現了InterestingEvent介面的CallMe例項傳遞給
//EventNotifier。也就是所謂的介面回調了。
en = new EventNotifier(this); // 建立事件通知程式,並將自身引用傳遞給它。
}
// 為事件定義實際的處理程式。
public void interestingEvent() {
System.out.println("Call me Hello.");
}
}
下面寫個測試類。
Test.java
package org.zj.sample;
public class Test {
public static void main(String[] args) {
EventNotifier en=new EventNotifier(new CallMe());
en.setHappened();
en.doWork();
}
}
結果:
Call me Hello.
1.5 通過例子深入理解
所謂回撥:就是A類
中呼叫B類
中的某個方法C
,然後B類
中反過來呼叫A類
中的方法D
,D
這個方法就叫回調方法,這樣子說你是不是有點暈暈的,看了人家說比較經典的回撥方式:
- Class A實現介面CallBack callback——背景1
- class A中包含一個class B的引用b ——背景2
class B
有一個引數為callback
的方法f(CallBack callback)
——背景3
A的物件a呼叫B的方法 f(CallBack callback)
——A類呼叫B類的某個方法 C
然後b就可以在f(CallBack callback)
方法中呼叫A的方法 ——B類呼叫A類的某個方法D
/**
* 這是一個回撥介面
* @author xiaanming
*
*/
public interface CallBack {
/**
* @param result 是答案
*/
public void solve(String result);
}
/**
* @author xiaanming
* 實現了一個回撥介面CallBack,相當於----->背景一
*/
public class Wang implements CallBack {
/**
* 相當於----->背景二
*/
private Li li;
/**
* @param li
*/
public Wang(Li li){
this.li = li;
}
/**
* @param question
*/
public void askQuestion(final String question){
//這裡用一個執行緒就是非同步,
new Thread(new Runnable() {
@Override
public void run() {
/**
* 這就相當於A類呼叫B的方法C
*/
li.executeMessage(Wang.this, question);
}
}).start();
play();
}
public void play(){
System.out.println("我要逛街去了");
}
/**
*
*/
@Override
public void solve(String result) {
System.out.println("小李告訴小王的答案是--->" + result);
}
}
/**
* @author
*
*/
public class Li {
/**
* 相當於B類有引數為CallBack callBack的f()---->背景三
* @param callBack
* @param question 小王問的問題
*/
public void executeMessage(CallBack callBack, String question){
System.out.println("小王問的問題--->" + question);
//模擬事情需要很長時間
for(int i=0; i<10000;i++){
}
/**
* 小李辦完自己的事情之後想到了答案是2
*/
String result = "答案是2";
/**
* 於是就打電話告訴小王,呼叫小王中的方法
* 這就相當於B類反過來呼叫A的方法D
*/
callBack.solve(result);
}
}
/**
* 測試類
* @author xiaanming
*
*/
public class Test {
public static void main(String[]args){
Li li = new Li();
Wang wang = new Wang(li);
wang.askQuestion("1 + 1 = ?");
}
}
通過上面的那個例子你是不是差不多明白了回撥機制呢,
再看下面的例子
//這個是View的一個回撥介面
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
package com.example.demoactivity;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
/**
* 這個就相當於Class A
* @author xiaanming
* 實現了 OnClickListener介面---->背景一
*/
public class MainActivity extends Activity implements OnClickListener{
/**
* Class A 包含Class B的引用----->背景二
*/
private Button button;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.button1);
/**
* Class A 呼叫View的方法,而Button extends View----->A類呼叫B類的某個方法 C
*/
button.setOnClickListener(this);
}
/**
* 使用者點選Button時呼叫的回撥函式,你可以做你要做的事
* 這裡我做的是用Toast提示OnClick
*/
@Override
public void onClick(View v) {
Toast.makeText(getApplication(), "OnClick", Toast.LENGTH_LONG).show();
}
}
下面是View
類的setOnClickListener
方法,就相當於B類咯,只把關鍵程式碼貼出來
/**
* 這個View就相當於B類
* @author xiaanming
*
*/
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnClickListener mOnClickListener;
/**
* setOnClickListener()的引數是OnClickListener介面------>背景三
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//這個不就是相當於B類呼叫A類的某個方法D,這個D就是所謂的回撥方法咯
mOnClickListener.onClick(this);
return true;
}
return false;
}
}