【问题标题】:While iterating over a collection of event handlers, how do you safely remove a handler from *within* a callback?在遍历事件处理程序集合时,如何安全地从回调中移除处理程序?
【发布时间】:2011-07-09 04:42:45
【问题描述】:

我有点困惑。 Java 的文档告诉我们,在使用 Iterator 对象迭代该集合时从集合中删除项目时没有定义的行为,唯一安全的方法是使用 Iterator.remove()。

那么,如果在遍历列表的过程中,其中一个处理程序决定是时候将自己作为侦听器删除,那么您将如何安全地从 ArrayList 中删除事件处理程序?

// in public class Dispatcher

public void dispatchEvent(){
    Iterator<IEventHandler> iterator = mHandlers.iterator();
    IEventHandler handler = null;
    while(iterator.hasNext()){
        handler = iterator.next();
        handler.onCallbackEvent();
    }
}

public void insertHandler(IEventHandler h){
    mHandlers.add(h);
}

public void removeHandler(IEventHandler h){
    mHandlers.remove(h);
}

同时,处理程序是这样实例化的......

final Dispatcher d = new Dispatcher();
d.insertHandler(new IEventHandler(){
    @Override
    public void onCallbackEvent(){
        Log.i(" callback happened ");
        d.removeHandler(this);
    }
});

看到潜在的问题了吗?由于在该特定处理程序中声明的 onCallbackEvent(),您正在从 ArrayList 中删除处理程序,而您仍在使用迭代器进行迭代

这是一个棘手的问题吗?处理这种情况的安全方法是什么?

【问题讨论】:

    标签: java android events iterator arraylist


    【解决方案1】:

    这是实现事件系统时非常常见的问题。唯一的解决方案是在更改时复制处理程序列表。您可以在 insertHandler/removeHandler 方法中自己执行此操作,也可以仅使用 CopyOnWriteArrayList。

    【讨论】:

    • 感谢您的建议。这确实很不幸。如果 Iterator 不能提供一种在迭代时修改底层集合的安全方法,我不得不想知道它的意义何在。 **注意:是的,我知道迭代器设计模式。不过,这种安全性似乎是迭代器应该为你买的东西,如果你不小心使用它的话。
    • 为了给您正在寻找的迭代安全性,迭代器必须复制或执行同样计算密集型的操作。你能自己想出另一种方法来实现一个安全的迭代器吗?您必须在更改时复制或在迭代时复制。在大多数情况下,更改时复制更合适,因为更改的频率低于迭代。默认集合不提供迭代安全性,因为并非所有情况都需要它。这就是为什么有 ArrayList 和 CopyOnWriteArrayList。
    • "copy the handlers on change":你的意思是复制removeHandler中的数组,然后在dispatchEvent结束时用修改后的副本替换原来的列表? (使用 CopyOnWriteArrayList 似乎要简单得多。)
    • “在变化时复制处理程序列表”是指在调用 addListener/removeListener 方法期间复制列表,而不是在事件调度期间。
    【解决方案2】:

    您可以重新实现 removeHandler 来存储计划删除的处理程序。

    public void removeHandler(IEventHandler h){
        mHandlersToRemove.add(h);
    }
    

    然后在您进行任何调度之前将其删除。

    public void dispatchEvent(){
        mHandlers.removeAll(mHandlersToRemove);
        mHandlersToRemove.clear();
        ...
    

    您也可以在 dispatchEvent 的末尾删除,但您只能从 within 处理程序中删除。 (否则,您可能会分派给已删除的处理程序。)


    如果您对此问题的理论解决方案感兴趣,可以查看 C++ 如何实现迭代器。在 stl 向量中,迭代器有一个 erase method,它返回下一个有效的迭代器。

    看起来像这样:

    for (itr = listA.begin(); itr != listA.end(); )
    {
        if ( shouldRemove(*itr) ) {
            itr = listA.erase(itr);
        }
        else {
          ++itr;
        }
    }
    

    当然,此示例不适用于您的问题,因为它都在 C++ 中,并且将新迭代器传播到顶层循环(或在您的调用中添加返回值以“删除“ 健康)状况)。但也许那里有类似的java实现:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-23
      • 1970-01-01
      • 1970-01-01
      • 2013-12-24
      • 1970-01-01
      相关资源
      最近更新 更多