Java事件模型
在我的前兩篇介紹C#事件和委托的blog 發表之後,大家響應特別熱烈,點擊率很高,看來事件/委托機制是很多同仁比較模糊的地方,借此東風,加上最近自己轉戰java,於是決定寫這篇介紹java事件機制的blog。
其實,不管哪種語言的事件機制,毫無例外都逃不出三點:事件源/發送者,事件的接受者/處理者/偵聽者,以及事件源向事件接受者傳遞的事件信息。對應在java中,事件源 (event source),事件傾聽者 (event listener),事件消息稱為eventobject。而在C#中,分別是發送者(Sender),處理者(handler),事件消息則是事件參數(EventArgument)。而java和c#都采用相同的響應模式:發布者/訂閱者模式(publisher/subscriber),具體來說就是:
(1)訂閱者向發布者注冊自己感興趣的事件;
(2)事件發生時,通知訂閱者響應事件。
簡單一句話,就是那句常說的:“Don't call me,I'll call you。”
由於之前已經對C#的事件機制進行了探討,下面,我將著重談談java的事件機制,以及對兩者實現機制的比較:
(一)Java事件實現機制
下面是一個自定義java事件的例子,通過這個簡單的演示,你可以看到Java的事件實現機制。這裡說明一下,這個例子引自http://www.rainsts.net/article.asp?id=224,為了說明原理,改編了原文中對匿名方法部分,而且由於這個網站的代碼編輯器缺乏對java代碼的支持,因此,對關鍵字,類型等並沒有作格式顯示處理。
import java.util.*;
// 定義一個類似 C# EventArgs 的類用來傳遞事件狀態信息。
// 一般要求繼承自 java.util.EventObject,且以 Event 結尾。
class ClicktEvent extends EventObject
{
public DemoBean source;
//構造函數的參數傳遞產生事件的事件源
public ClickEvent(DemoBean source)
{
super(source);
this.source = source;
}
}
// 通過接口來定義事件響應函數原型,就像c# delegate定義了響應函數的“模板”,
// 別忘了,接口實際上就是一種“合同”,“契約”,通過這個接口中的函數簽名達到對響應函數的規范
// 一般要求繼承自 java.util.EventListener,且以 Listener 結尾。
//這裡以I開頭定義ClickListener接口,借鑒自.net,不符合j2ee的命名規范
interface IClickListener extends EventListener
{
void click(ClicktEvent e);
}
//事件偵聽者,實現偵聽者接口
public class ClickListener implements IClickListener
{
public void click(ClicktEvent e) {
System.out.println( "the clicked event happened");
}
}
// 定義演示控件類,也就是事件源
class DemoBean
{
// 用一個java.util.Vector對象來存儲所有的事件監聽器對象。
private Vector clicks = new Vector();
// 添加事件訂閱。一般以 add( listener)方式拼寫,並添加 synchronized 關鍵字。
public synchronized void addExampleListener(IClickListener listener)
{
clicks.add(listener);
}
// 移除事件訂閱。一般以 remove( listener)方式拼寫,並添加 synchronized 關鍵字。
public synchronized void removeExampleListener(IClickListener listener)
{
clicks.remove(listener);
}
// 觸發事件。
protected void doClickEvent()
{
// 鎖定,避免在觸發期間有事件被訂閱或移除。
synchronized (this)
{
// 創建事件狀態對象。
ClicktEvent ce = new ClickEvent(this);
// 循環觸發所有的事件訂閱方法。
for (int i = 0; i < clicks.size(); i++)
{
IClickListener e = (IClickListener)clicks.get(i);
e.click(ce);
}
}
}
// 模擬點擊操作。
public void Click()
{
doClickEvent();
}
}
//測試程序
public class Program
{
public static void main(String[] args)
{
// 創建控件。
DemoBean bean = new DemoBean();
//實例化一個事件偵聽者
ClickListner testListner=new ClickListner();
// 添加事件訂閱。
bean.addExampleListener (testListner);
// 模擬觸發點擊操作。
bean.Click();
}
}
輸出結果:the clicked event happened
(二)事件實現機制的比較(Java/C#)
總體來說,c#沿襲了C/C++中的函數回調機制,通過委托對函數指針的封裝來實現對響應函數的調用;而java則通過接口來規范響應函數,使用多態的方式在運行時實現對事件接收者的響應函數的調用,應該說,這才是一種面向對象的機制。當然,兩種方式各有千秋,下表是對兩者的比較:
java c# 說明 效率 采用多態,相對高 采用委托,相對低。 應該說,它們都是在運行時獲取對哪個對象的哪個方法進行調用,但是采用多態相對於委托效率高一點。 是否支持靜態方法調用 否 是 java采用多態,當然不能把方法聲明為static;c#中delegate中的_object可以為null來實現對靜態方法的調用 是否類型安全 是 是 它們都會在編譯時對響應函數進行參數檢查,類型安全。 開發者易用性 兩級實體對象:事件源-與事件響應者 三級實體對象:事件源-委托-事件響應者 由於有delegate的封裝,不用編寫事件注冊/注銷之類的代碼,c#事件處理易用性相對高;注意,雖然java中采用接口來規范響應函數,但這裡卻說java中是兩級實體對象,是因為在運行時並不存在接口的實例(實際上,接口也不可實例化,呵呵)