Java 理论与实践 做个好的(事件)侦听器

  • 日期:09-01
  • 点击:(915)


事件侦听器编写和支持指南

Swing框架以事件侦听器的形式广泛使用观察者模式(也称为发布 - 订阅模式)。 Swing组件充当用户交互的目标,在用户与之交互时触发事件;数据模型类在数据更改时触发事件。以这种方式使用观察者通过将控制器与模型分离并将模型与视图分离来简化GUI应用程序的开发。

件,从而促进组件重用。

AWT和Swing组件(如JButton或JTable)使用观察者模式来消除GUI事件生成与给定应用程序中的语义之间的耦合。类似地,Swing的模型类(如TableModel和TreeModel)也使用观察器来消除数据模型表示和视图生成之间的耦合,支持相同数据的多个独立视图。 Swing定义了Event和EventListener对象层次结构;可以生成事件的组件,例如JButton(Visual Component)或TableModel(Data Model),为侦听器注册和取消提供addXxxListener()和removeXxxListener()方法。注册。这些类负责决定何时需要触发事件,何时实际触发事件以及何时调用所有已注册的侦听器。

为了支持侦听器,对象需要维护已注册侦听器的列表,提供侦听器注册和注销的方法,并在发生适当事件时调用每个侦听器。它易于使用和支持监听器(不仅仅是在GUI应用程序中),但是您应该避免注册界面两侧的一些缺陷(这些是支持监听器的组件和注册监听器的组件)。

线程安全问题

通常,调用侦听器的线程与使用侦听器的线程不同。为了支持从不同线程注册侦听器,无论用于存储和管理活动侦听器列表的机制如何,此机制都必须是线程安全的。 Sun的文档中的许多示例都使用Vector来保存侦听器列表,这可以解决一些问题,但并不能解决所有这些问题。当事件触发时,触发它的组件会考虑迭代侦听器列表并调用每个侦听器,这会带来并发修改的风险,例如线程意外地想要在侦听器列表迭代期间添加。或者删除一个监听器。

管理侦听器列表

假设您使用Vector来保存侦听器列表。尽管Vector类是线程安全的(意味着它可以在没有额外同步的情况下调用,但没有破坏Vector数据结构的风险),但是如果在集合期间聚合,则集合的迭代包含“检测然后执行”序列。迭代被修改后,存在失败的风险。假设在迭代开始时列表中有三个侦听器。迭代Vector时,重复调用size()和get()方法,直到检索完所有元素,如清单1所示:

清单1. Vector

的不安全迭代向量v; for(int i=0; i v.get(i).eventHappened(event);

但是如果有人在最后一次调用Vector.size()之后从列表中删除了一个监听器会发生什么?现在,Vector.get()将返回null(这是真的,因为它的状态自上次检测到向量的状态以来已经改变),并且在尝试调用eventHappened()时抛出NullPointerException。这是“检测然后执行”序列的一个示例 - 检测是否有更多元素,如果有,则获取下一个元素 - 但是在存在并发修改的情况下,状态可能自检测后发生了变化。图1

图1.导致意外故障的并发迭代和修改