【问题标题】:Why are listener lists Lists?为什么是侦听器列表 Lists?
【发布时间】:2011-02-25 15:57:32
【问题描述】:

为什么侦听器列表(例如在 Java 中使用 addXxxListener()removeXxxListener() 注册和注销侦听器的那些)称为 lists,并且通常实现为 ListsSet 不是更合适,因为对于听众来说,有

  • 无论以何种顺序调用它们(尽管可能有这样的需求,但它们是特殊情况;普通的侦听器机制无法保证),并且
  • 无需多次注册同一个监听器(这样做是否会导致调用同一个监听器 1 次或 N 次,或者是错误,是另一个问题)

这只是传统的问题吗?无论如何,集合是某种类型的列表。有性能差异吗?迭代List 比迭代Set 快还是慢?是否占用更多或更少的内存?差异当然几乎可以忽略不计。

【问题讨论】:

  • '因为它是一个list'ener? :p

标签: java list listener


【解决方案1】:

什么样的套装?所有侦听器都应该实现 equals 和 hashCode 以便使用散列集,还是使用身份散列集?将侦听器添加到列表中的用例是否值得复杂性两倍?是否有一种简单的机制可以使集合在调用其处理程序期间不会添加或删除侦听器?

可能存在一些性能差异,但肯定有更复杂的设计,它强制将多次添加-多次删除决定放入库中,而不是将其留给应用程序。

【讨论】:

  • 我认为身份哈希集是合适的。但是当然,没有开箱即用的“CopyOnWriteIdentityHashSet”,因此使用通用列表习语可能会更简单地获得正确的最终结果。
  • 但是:Java 6 中确实有一个 CopyOnWriteArraySet,它是线程安全的(“通过迭代器遍历速度很快,不会遇到其他线程的干扰。”)。根据java.sun.com/javase/6/docs/technotes/guides/collections/…,“此实现非常适合维护必须防止重复的事件处理程序列表。”
  • @Joonas 由于 Swing 处理程序是在 Swing 线程中调用的,因此问题总是在修改底层集合并使迭代器无效,而不是并发更新。但如果你确实想要这些语义,你当然可以使用写时复制集。
  • CopyOnWriteXXX 的妙处在于:修改底层集合不会使迭代器失效。
【解决方案2】:

侦听器列表是列表(而不是集合)的一个重要原因也解释了为什么您经常看到它们被向后迭代。一个常见的场景涉及一个监听器,当它收到一些更改的通知时,它会将自己作为一个监听器移除。如果监听器被存储为一个列表并向前迭代(或存储为一个集合并以某种不确定的顺序迭代),将其自身作为监听器移除将导致 ConcurrentModificationException。

因此,侦听器被存储为列表并以倒序通知。然后,如果某个侦听器在收到通知时将自己从侦听器列表中删除,则不会导致 ConcurrentModificationException 或移动其他尚未通知的侦听器的索引。

【讨论】:

  • 不关心订单怎么办?
  • 如果您的听众曾经因为收到通知而移除自己,您应该小心。否则,您不会因为保留订单而失去任何东西。
  • 如果他们自己删除了,我为什么要关心?我认为在大多数情况下,使用一组是可以的。与其他侦听器相比,侦听器通常不应假设何时调用它们。也许 SkipList 是解决这个问题的中间解决方案?奇怪,我找不到它的 Java 类。
  • 如果您正在遍历一组侦听器,通知每个侦听器,其中一个从您当前正在遍历的侦听器集中删除自己,您可能收到 ConcurrentModificationException .这个问题当然还有其他解决方案,但是将监听器存储在一个列表中并以倒序通知它们肯定是一个简单的解决方案。
  • 哦,你的意思是在调用它的一个函数时删除监听器......是的,这可能是有问题的,但你可以通过多种方式解决这个问题(你的假设很多关于它是如何实现的因为也可能涉及多个线程,而且它破坏了您非常想要的整个顺序概念),就像您所写的那样。一种方法是使用并发集。另一种是拥有另一组等待删除的侦听器,这些侦听器将在对所有侦听器的调用完成后被删除。
【解决方案3】:

你完全正确。侦听器应添加到集合中。正如您所说,多次添加侦听器是没有意义的。此外,如果您使用集合,则不会依赖听众的顺序。这就是最重要的一点:如果您认真对待软件开发以及我们发现的每一条引导我们进行更好设计的原则:隔离、独立、责任,则不应依赖它。

这里提到的每个方面(多线程,性能,......)首先必须从属于自己,但如果你有充分的理由,之后可能会被打破。我的意思是非常好的理由。

顺便说一句:让侦听器自行移除是一种不好的做法。添加和删​​除应该是对称的。所以监听器应该通过注册它的对象来移除。如果你有很多听众,你很快就会陷入困境。

【讨论】: