<b>Java理论与实践:做个好的(事件)侦听器</b>[Java编程]
本文“<b>Java理论与实践:做个好的(事件)侦听器</b>[Java编程]”是由七道奇为您精心收集,来源于网络转载,文章版权归文章作者所有,本站不对其观点以及内容做任何评价,请读者自行判断,以下是其具体内容:
察看者情势在 Swing 开辟中很常见,在 GUI 利用程序以外的场景中,它对 于消除组件的耦合性也非常有效.但是,仍旧存在一些侦听器登记和调用方面的 常见缺陷.在 Java 理论与实践 的这一期中,Java 专家 Brian Goetz 就若何 做一个好的侦听器,以及若何对您的侦听器也友好,供应了一些感受很好的倡议 .请在呼应的 谈论论坛 上与作者和其他读者分享您对这篇文章的设法.(您也 可以单击本文顶部或底部的 谈论 拜候论坛.)
Swing 框架以事件侦听器的情势遍及操纵了察看者情势(也称为公布-定阅模 式).Swing 组件作为用户交互的目标,在用户与它们交互的时刻触发事件;数 据模子类在数据发生改变时触发事件.用这种方法利用察看者,可以让掌握器与 模子别离,让模子与视图别离,从而简化 GUI 利用程序的开辟.
“四人帮”的 计划情势 一书(参阅 参考资料)把察看者情势描写为:定义 对象之间的“一对多”关系,这样一个对象改变状况时,全部它的依靠项城市被 告诉,并自动更新.察看者情势支持组件之间的疏松耦合;组件可以保持它们的 状况同步,却不需求直接知道彼此的标识或内部情形,从而增长了组件的重用.
AWT 和 Swing 组件(比方 JButton 或 JTable)利用察看者情势消除了 GUI 事件生成与它们在指定利用程序中的语义之间的耦合.近似地,Swing 的模子类 ,比方 TableModel 和 TreeModel,也利用察看者消除数据模子表示 与视图生 成之间的耦合,从而支持相同数据的多个独立的视图.Swing 定义了 Event 和 EventListener 对象层次构造;可以生成事件的组件,比方 JButton(可视组件 ) 或 TableModel(数据模子),供应了 addXxxListener() 和 removeXxxListener() 办法,用于侦听器的登记和撤消登记.这些类负责决意什 么时刻它们需求触发事件,什么时刻确切触发事件,以及什么时刻调用全部登记 的侦听器.
为了支持侦听器,对象需求保护一个已登记的侦听器列表,供应侦听器登记 和撤消登记的手段,并在得当的事件发生时调用每个侦听器.利用和支持侦听器 很简单(不但仅在 GUI 利用程序中),但是在登记接口的两边(它们是支持侦 听器的组件和登记侦听器的组件)都该当避免一些缺陷.
线程安全问题
普通,调用侦听器的线程与登记侦听器的线程差别.要支持从差别线程登记 侦听器,那么不管用什么机制存储和管理活动侦听器列表,这个机制都必须是线 程安全的.Sun 的文档中的很多示例利用 Vector 保存侦听器列表,它办理了部 分问题,但是没有办理全部问题.在事件触发时,触发它的组件会考虑迭代侦听 器列表,并调用每个侦听器,这就带来了并发改正的风险,比方在侦听器列表迭 代期间,某个线程无意想增添或删除一个侦听器.
管理侦听器列表
假定您利用 Vector<Listener> 保存侦听器列表.固然 Vector 类是 线程安全的(意味着不需求举行额外的同步便可调用它的办法,没有破坏 Vector 数据构造的风险),但是调集的迭代中包含“检测然后履行”序列,如 果在迭代期间调集被改正,就有了失利的风险.假定迭代开始时列表中有三个侦 听器.在迭代 Vector 时,反复调用 size() 和 get() 办法,直到全部元素都 检索完,如清单 1 所示:
清单 1. Vector 的不安全迭代Vector<Listener> v;
for (int i=0; i<v.size(); i++)
v.get(i).eventHappened(event);
但是,假如刚好就在最后一次调用 Vector.size() 之后,有人从列表中删除 了一个侦听器,会发生什么呢?目前,Vector.get() 将返回 null (这是对的 ,因为从上次检测 vector 的状况以来,它的状况已经变了),而在试图调用 eventHappened() 时,会抛出 NullPointerException.这是“检测然后履行” 序列的一个示例 —— 检测能否存在更多元素,假如存在,就获得下一元素 — — 但是在存在并发改正的情形下,检测之后状况大概已经改变.图 1 演示了这 个问题:
图 1. 并发迭代和改正,造成意料之外的失利
这个问题的一个办理筹划是在迭代期间持有对 Vector 的锁;另一个筹划是 克隆 Vector 或调用它的 toArray() 办法,在每次发闹事件时检索它的内容. 全部这两个办法都有性能上的问题:第一个的风险是在迭代期间,会把其他想访 问侦听器列表的线程锁在表面;第二个则要成立暂时对象,并且每次事件发生时 都要拷贝列表.
假如用迭代器(Iterator)去遍历侦听器列表,也会有一样的问题,只是表 现略有差别; iterator() 实现不抛出 NullPointerException,它在探测到迭 代开始之后调集发生改正时,会抛出 ConcurrentModificationException.一样 ,也可以通过在迭代期间锁定调集避免这个问题.
java.util.concurrent 中的 CopyOnWriteArrayList 类,可以帮忙避免这个 问题.它实现了 List,并且是线程安全的,但是它的迭代器不会抛出 ConcurrentModificationException,遍历期间也不要求额外的锁定.这种特点 组合是通过在每次列表改正时,在内部重新分配并拷贝列表内容而实现的,这样 ,遍历内容的线程不需求处理改变 —— 从它们的角度来说,列表的内容在遍历 期间保持不变.固然这听起来大概没效率,但是请记着,在大都察看者情形下, 每个组件只有少量侦听器,遍历的数目远远超越插入和删除的数目.所以更快的 迭代可以补偿较慢的改变历程,并供应更好的并发性,因为多个线程可以同时迭 代列表.
以上是“<b>Java理论与实践:做个好的(事件)侦听器</b>[Java编程]”的内容,如果你对以上该文章内容感兴趣,你可以看看七道奇为您推荐以下文章:
本文地址: | 与您的QQ/BBS好友分享! |