关于Java的事件分派机制及两个可能的语言扩展
来源:互联网 发布:智慧芽数据库 编辑:程序博客网 时间:2024/06/18 09:27
public interface MouseListener { void mouseClicked(MouseEvent e); void mouseEntered(MouseEvent e); void mouseExited(MouseEvent e); void mousePressed(MouseEvent e); void mouseReleased(MouseEvent e);}public interface MouseMotionListener { void mouseDragged(MouseEvent e); void mouseMoved(MouseEvent e);}public class MyPanel extends Panel implements MouseListener { private Canvas canvas = ...; public MyPanel() { ... canvas.addMouseListener(this); } public void mouseEntered(MouseEvent e) { ... } public void mouseExited(MouseEvent e) { ... } public void mousePressed(MouseEvent e) { ... } public void mouseReleased(MouseEvent e) { ... }}
由于注册事件时仅用到一个字段,而interface中不同事件的分派由类(如例中的MyPanel)的vtbl支持,被类的所有对象共享;因而当类有多个实例时,Java的事件注册机制较C#为每个事件保存单独的delegate字段更为节省。如果上面的代码用C#实现,那么所有MyPanel的实例都重复相同的mouseEntered、mouseExited等字段。
应当补充指出,在我的印象中早期JDK(1.1?)AWT控件似乎用1个AWTEventMulticaster字段来注册所有不同类型的EventListener,因而较节省。我在网上看到的一些API文档(包括AWTEventMulticaster的实现)与我的印象不符。我认为一方面私人或公司有权建立自己的知识库(如光盘资料),国家依赖于它们的专业知识而不是反过来;另一方面,篡改API等资料是不可容忍的自贬人格的行为,会导致中国人受歧视。
当需要注册多个EventListener,尤其需要为不同对象注册相同类型的EventListener时,匿名EventListener类型常被使用。
public class MyPanel extends Panel implements MouseListener { private Canvas canvas1 = ...; private Canvas canvas2 = ...; public MyPanel() { ... canvas1.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { ... } public void mousePressed(MouseEvent e) { ... } ... }); canvas2.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { ... } public void mousePressed(MouseEvent e) { ... } ... }); }}
不过,匿名EventListener类型导致更多的class loading与对象实例,可能成为系统运行的负担。(Java虚拟机可以做的是,由于EventListener对象与所属控件对象(MyPanel)一一对应的关系,不另外为EventListener分配对象空间,而将其嵌入所属控件(MyPanel)的对象空间内,并且优化掉OuterClass.this字段。)
不难想像如下的语言扩展能够简化语法,并帮助Java虚拟机更高效地执行。public class MyPanel extends Panel implements MouseListener-canvas1, MouseListener-canvas2 { private Canvas canvas1 = ...; private Canvas canvas2 = ...; public MyPanel() { ... canvas1.addMouseListener((-canvas1)this); canvas2.addMouseListener((-canvas2)this); } private void mouseEntered-canvas1(MouseEvent e) { ... } private void mousePressed-canvas1(MouseEvent e) { ... } ... private void mouseEntered-canvas2(MouseEvent e) { ... } private void mousePressed-canvas2(MouseEvent e) { ... } ...}
例中,MyPanel实现了两个MouseListener接口,用后缀予以区别;不同接口的实现方法不同。例中的代码与下述实现等效;注意,因为interface的实现方法是private,所以两个interface也被声明为private,因而MyPanel的接口声明对外不可见。
public class MyPanel extends Panel implements MyPanel.MouseListener$canvas1, MyPanel.MouseListener$canvas2 { private interface MouseListener$canvas1 extends MouseListener {} private interface MouseListener$canvas2 extends MouseListener {} private Canvas canvas1 = ...; private Canvas canvas2 = ...; public MyPanel() { ... canvas1.addMouseListener((MouseListener$canvas1)this); canvas2.addMouseListener((MouseListener$canvas2)this); } private void MouseListener$canvas1.mouseEntered(MouseEvent e) { ... } private void MouseListener$canvas1.mousePressed(MouseEvent e) { ... } ... private void MouseListener$canvas2.mouseEntered(MouseEvent e) { ... } private void MouseListener$canvas2.mousePressed(MouseEvent e) { ... } ...}
在同一类中多次实现相同接口,在JUnit的测试代码中也很有用。在下面的例子中,test1与test2有相同的setup()与tearDown(),但有各自的run()方法。注意,例子仅用到Java语言本身而未依赖注解及反射,因而代码执行效率更高。
public class MyTest implements TestCase-test1, TestCase-test2 { public void setup() { ... } public void tearDown() { ... } public void run-test1() { ... } public void run-test2() { ... }}public class Runner { public void run(TestCase testCase) { ... } public void run(Object test) { for (TestCase t : TestCase.class.casts(test)) run(t); }}
另一个可能的语言改进是动态分派(dynamic dispatch,有时也称double dispatch),即根据参数的运行时类型动态在多个方法之中选择最匹配的一个。
public interface VirtualEventListener { default void processEvent(virtual AWTEvent evt) {}}public MyPanel extends Panel implements VirtualEventListener { private Canvas canvas = ...; public MyPanel() { ... canvas.addVirtualEventListener(this); } public void processEvent(MouseEvent evt) { ... } public void processEvent(KeyEvent evt) { ... }}
说明:
1. 接口声明中的virtual表示需要动态匹配的参数,一个方法可以同时包含多个需要动态匹配的参数(double dispatch),也可以同时有动态与非动态匹配的参数。2. 运行时的方法匹配与静态的方法overload相似,只是根据运行时的类型。Java虚拟机将各方法按参数类型的继承关系组织成树结构,并找出最深的匹配节点。(继承关系可能能用位码比较完成。有多个动态参数及需要匹配interface时更复杂。)
3. 按上述方法实现的动态分派开销比虚方法调用高,但能减少EventListener的字段数量(仅需1个字段)及vtbl的大小(仅处理用到的事件类型),并使代码清晰可读。
4. 事件按照源(source)的不同分派给不同的方法,也可能被合并到动态分派之中?
此外,double dispatch也可以通过两次虚函数调用实现,手写代码也不困难。
public class AWTEvent { ... public void processBy(Component component) { component.processEvent(this); }}public class MouseEvent extends AWTEvent { ... public void processBy(Component component) { component.processMouseEvent(this); }}public class KeyEvent extends AWTEvent { ... public void processBy(Component component) { component.processKeyEvent(this); }}public class Component { ... public void processEvent(AWTEvent evt) { evt.processBy(this); } protected void processEvent (Event evt) { } // AWT中已有这些方法, protected void processMouseEvent(MouseEvent evt) { } // 但未用double dispatch protected void processKeyEvent (KeyEvent evt) { } ...}public class MyComponent : Component { ... protected void processMouseEvent(MouseEvent evt) { } protected void processKeyEvent (KeyEvent evt) { }}
用虚方法实现的double dispatch效率要高得多。不过,这要求为新添的Event类型修改Component基类。如果Java编译工具或虚拟机能够代为生成double dispatch代码则要方便得多。另外需要指出的是,从Oracle获取的JDK AWT源码实际上相当混乱而不可读。
- 关于Java的事件分派机制及两个可能的语言扩展
- 关于任务的分派
- 面向对象语言的多分派、单分派、双重分派
- Java的静态分派和动态分派
- JAVA的静态分派和动态分派
- Android事件的分派处理
- JBPM的任务分派机制
- JBPM的任务分派机制
- WINX的消息分派机制
- JBPM的任务分派机制
- JBPM的任务分派机制
- JBPM的任务分派机制
- JBPM的任务分派机制
- JBPM的任务分派机制
- JBPM的任务分派机制
- WINX的消息分派机制
- WINX的消息分派机制
- WINX的消息分派机制
- First,FirstOrDefault,Single,SingleOrDefault的区别
- Knockout.js及MVVM模式介绍
- acm-UVA10071(水题)
- 二叉树测试,递归遍历,随后加上非递归遍历
- Divisibility+dfs+剪枝
- 关于Java的事件分派机制及两个可能的语言扩展
- 话说vector
- QRCode二维码生成方案及其在带LOGO型二维码中的应用(1)
- ConcurrentModificationException异常解决办法
- 学习的网站
- 耶稣有13个门徒,其中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个开始报号:1,2,3,1,2,
- MDI父窗体和子窗体之间怎么传值?
- Linux文件系统
- 解决安装win7后ubuntu无法启动问题