【问题标题】:Suitable collection class for event listeners in Java适合 Java 中的事件监听器的集合类
【发布时间】:2010-01-15 15:34:18
【问题描述】:

相关: Does java have a "LinkedConcurrentHashMap" data structure?


我正在寻找一个集合类来保存对事件侦听器的引用。

理想情况下,我希望该集合具有以下属性(按优先级排序):

  1. 维护插入顺序。较早的侦听器可能会取消该事件,从而阻止它被传递给后来添加的侦听器。如果使用诸如 HashSet 之类的类,其迭代器可能以错误的顺序返回元素,这将中断。
  2. 使用WeakReferences 以便侦听器列表不会阻止对侦听器进行垃圾收集。
  3. 集合是Set,因此会自动删除重复项。
  4. Iterator 是集合的线程安全快照,不受添加新侦听器的影响。还允许在多个线程上传递事件。 (这不是必需的 - 我可以迭代该集合的克隆。)

我知道一些课程满足部分但不是所有这些标准。例子:

  • java.util.LinkedHashSet(#1 和 #3)
  • java.util.WeakHashMap,由Collections.newSetFromMap 包裹(#2 和#3)
  • javax.swing.event.EventListenerList(需要一些额外的同步)(#1 和 #4)
  • java.util.concurrent.CopyOnWriteArraySet(#1、#3 和 #4)

但是 #1 和 #2 都没有。这样的类是否存在于某个库中?

【问题讨论】:

  • 监听器对象实现equals真的很典型吗?
  • 另外,保留对每个听众的强烈引用是谁的工作?如果我写了一个监听器来记录各种事件并注册它,当它在几分钟后突然消失时,我肯定会感到惊讶!
  • 没有等号的问题是:{Object o = new Object(); WeakReference r1 = new WeakReference(o), r2 = new WeakReference(o); return r1.equals(r2);} 返回false
  • 这对匿名监听器来说是个问题,但我的监听器是ListModel,所以它们通常会被 GUI 强烈引用。
  • 关于#2:我认为你不应该依赖垃圾收集来清理你的烂摊子。如果你在某个地方注册了一个监听器,你应该自己注销它,不要指望它会在某个时间点自动消失。通常,侦听器在触发时会执行某些操作,并且由于您无法知道垃圾收集何时开始,因此您将获得非常不确定的行为...

标签: java collections concurrency weak-references event-listener


【解决方案1】:

您可以使用 Wea​​kListeners(请参阅 http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/WeakListeners.html)和 CopyOnWriteArraySet。

  1. 在您的事件源中实现 remove(ListenerType listener) 方法。
  2. 在您的 register(SomeListener listener) 方法中,将 WeakListener 添加到集合中:

    listenerCollection.put((ListenerType)WeakListeners.create ( ListenerType.class, listener, this));

当真正的监听器从内存中移除时,弱监听器会被通知,并且它会注销自己。 (这就是为什么它需要引用源(this)进行注册。)取消注册是通过调用源的方法remove使用反射完成的。

【讨论】:

    【解决方案2】:

    我首先要说的是,您有几个要求放在一起是没有意义的。您正在寻找一个删除重复项并支持弱引用的集合,这向我表明侦听器可能会在不确定的时间出现和消失。然而,您想保持插入顺序,并允许一个侦听器取消所有后续通知。对我来说,这听起来像是难以找到错误的秘诀,我强烈建议重新考虑它。

    也就是说,您有一个几乎可以推动解决方案的要求:您不希望可能来自普通迭代器的ConcurrentModificationException。这意味着您将不得不复制原始列表。一路上,您可以检查并删除空引用:

    // the master list
    List<WeakReference<MyListener>> _list = new ArrayList<WeakReference<MyListener>>();
    
    // inside your send-notification method
    List<MyListener> toNotify = new ArrayList<MyListener>(_list.size());
    Iterator<WeakReference<MyListener>> itx = _list.iterator();
    while (itx.hasNext())
    {
        WeakReference<MyListener> ref = itx.next();
        MyListener lsnr = ref.get();
        if (lsnr != null)
            toNotify.add(lsnr);
        else
            itx.remove();
    }
    
    // now iterate "toNotify" and invoke the listeners
    

    你现在可能吓坏了,说“List!这是一个线性数据结构!我不能使用它,插入是 O(N)!”

    嗯,是的,你可以。我不知道你打算有多少听众。但只要您的数量

    从编码的角度来看,更有趣的是如何处理弱引用。您会注意到,在测试所指对象是否为空之前,我将其显式取消引用到一个变量中。在处理引用对象时,这是非常重要的代码:尽管在两次调用 get() 之间收集引用对象的可能性极小,但这是可能的。

    这让我想到了WeakReference 本身。您需要创建自己的子类来覆盖equals()hashCode() 方法以委托给其所指对象。我以为我只有这样一个类,但显然没有,所以将它留给你实现。

    【讨论】:

      【解决方案3】:

      Set 是与侦听器一起使用的正确集合。

      如果您依赖侦听器的插入顺序,您的设计就会被破坏。它错过了听众与其他听众隔离和独立的观点。使用集合而不是列表。

      如果您依赖 WeakReferences,您的设计就会被破坏。在您添加它的同一对象中删除侦听器。此 SYMMETRY 支持 READABILITY 和 MAINTAINABILITY。解决弱引用监听器忘记退订的编程错误,只是隐藏了问题。

      如果您将您的侦听器集合提供给其他对象而不是您观察到的对象,那么您的设计就会被破坏。保持 Set 私有以支持 ENCAPSULATION。

      如果您覆盖侦听器的 equals 和哈希码,您的设计就会被破坏。它隐藏了不必要的函数调用的问题。而是防止不必要的呼叫。毕竟重写等于和监听器的哈希码是不必要的。

      在 MULTITHREADING 环境中,在资源“侦听器”上放置一个 MONITOR,同时添加、删除或迭代它。您可以在迭代之前创建一个防御副本以避免 ConcurrentModificationException。那么迭代不必是同步的,但复制动作应该是同步的。

      必须调整或重新制定任何其他要求以匹配这些陈述。任何其他做法都会因为缺乏隔离性、独立性、封装性和清晰度而导致无法维护的代码、内存泄漏。

      【讨论】:

        【解决方案4】:

        您可以将每个侦听器引用包装在 WeakReference 中,然后使用 CopyOnWriteArraySet

        【讨论】:

        • 这有两个问题:1. 垃圾收集的引用将永远保留在集合中(WeakHashMap 会自动删除它们)和 2. WeakReference 不会覆盖 equals(),因此您最终会得到对集合中同一个侦听器的多个引用,但您无法判断它们是重复的。
        • 是的。不过,也许您可​​以通过扩展 CopyOnWriteArraySet 以在集合发生变异时检查引用对象而不是 WeakReference 并手动删除过时的引用来克服这个问题。我不相信标准集合中的任何东西都能提供您想要的开箱即用的一切。
        【解决方案5】:

        您可以扩展 WeakReference 以覆盖等于和哈希码,然后您可以在 LinkedHashSet 中使用它们。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-04-10
          • 1970-01-01
          • 1970-01-01
          • 2012-12-09
          • 1970-01-01
          • 2013-12-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多