Java 侦听器应该能够删除侦听器吗?
许多类使用类似于以下的代码来激发侦听器Java 侦听器应该能够删除侦听器吗?,java,Java,许多类使用类似于以下的代码来激发侦听器 private List<Listener> listeners = new ArrayList<Listener>(); public void fireListener() { for(Listener l : listeners) l.someMethod(); } 您可以先复制一份侦听器集合,以避免可能的ConcurrentModificationException: public void fireListener
private List<Listener> listeners = new ArrayList<Listener>();
public void fireListener() {
for(Listener l : listeners) l.someMethod();
}
您可以先复制一份
侦听器
集合,以避免可能的ConcurrentModificationException
:
public void fireListener() {
Listener[] ll = listeners.toArray(new Listener[listeners.size()]);
for(Listener l : ll) l.someMethod();
}
无法向正在使用的列表/映射添加/删除。在C#中有一个很好的小类,叫做ConcurrentList/ConcurrentDictionary 在Java中,这是可能的,这里有一个很好的链接:
由于集合在循环中使用,因此该集合将在特定时间内使用。如果在循环内调用的函数中添加/删除侦听器,将出现一个错误,因为在没有并发的情况下不允许添加/删除侦听器。在浏览侦听器时处理添加/删除侦听器的正确方法是使用支持此操作的迭代器 例如:
// Assuming the following:
final List<Listener> listeners = ...
final Iterator<Listener> i = listeners.iterator();
while (i.hasNext()) {
// Must be called before you can call i.remove()
final Listener listener = i.next();
// Fire an event to the listener
i.notify();
if (/* true iff listener wants to be deregistered */) {
i.remove(); // that's where the magic happens :)
}
}
//假设如下:
最终列表侦听器=。。。
final Iterator i=listeners.Iterator();
while(i.hasNext()){
//在调用i.remove()之前必须先调用
最终侦听器=i.next();
//向侦听器触发事件
i、 通知();
如果(/*true iff侦听器希望取消注册*/){
i、 remove();//这就是神奇发生的地方:)
}
}
NB:请注意,某些迭代器不支持迭代器#remove
方法
我想说,允许侦听器添加/删除其他侦听器(或自己)不是一个好主意。这表明关注点分离不良。毕竟,为什么听者应该知道关于调用者的任何事情?您将如何测试这样一个紧密耦合的系统
相反,您可以让事件处理方法(在侦听器中)返回一个布尔标志,指示侦听器不希望接收更多事件。这使得事件调度器有责任进行删除,并且它涵盖了大多数需要从侦听器中修改侦听器列表的用例
这种方法的主要区别在于,监听器只是简单地说了一些关于自己的事情(即“我不想要更多的事件”),而不是与调度器的实现联系在一起。这种解耦提高了可测试性,并且不会向另一个类公开任何一个类的内部
public interface FooListener {
/**
* @return False if listener doesn't want to receive further events.
*/
public boolean handleEvent(FooEvent event);
}
public class Dispatcher {
private final List<FooListener> listeners;
public void dispatch(FooEvent event) {
Iterator<FooListener> it = listeners.iterator();
while (it.hasNext()) {
if (!it.next().handleEvent(event))
it.remove();
}
}
}
公共接口傻瓜监听器{
/**
*@return False,如果侦听器不想接收更多事件。
*/
公共布尔handleEvent(FooEvent事件);
}
公共类调度器{
私人最终听众名单;
公共无效调度(FooEvent事件){
Iterator it=listeners.Iterator();
while(it.hasNext()){
如果(!it.next().handleEvent(事件))
it.remove();
}
}
}
更新:在监听器中添加和删除其他监听器的问题稍微多一些(并且应该引起更大的警钟),但您可以遵循类似的模式:监听器应该返回关于需要添加/删除哪些其他监听器的信息,调度器应该根据该信息采取行动
但是,在这种情况下,您会遇到很多边缘情况:
- 当前事件会发生什么
- 是否应将其发送给新添加的侦听器李>
- 是否应将其发送给即将移除的对象李>
- 如果您试图删除列表中较早的内容,并且事件已发送到该内容,该怎么办
- 另外,如果侦听器X添加了侦听器Y,然后删除了侦听器X,该怎么办?你应该去吗李>
所有这些问题都来自监听器模式本身,以及列表中所有监听器相互独立的基本假设。处理这些情况的逻辑应该在调度器中,而不是在侦听器中
更新2:在我的示例中,为了简洁起见,我使用了一个裸布尔值,但在实际代码中,我会定义一个两值枚举类型,以使契约更加明确。有三种情况:
您不希望在侦听器执行期间允许修改侦听器集合:
在这种情况下,ConcurrentModificationException
是合适的
您希望允许修改侦听器,但更改不会反映在当前运行中:
您必须确保对侦听器的修改不会对当前运行产生影响。一个CopyOnWriteArrayList
就可以做到这一点。在使用它之前,请阅读API,其中有一些陷阱。
另一个解决方案是在遍历列表之前复制它
您希望对侦听器的更改反映在当前运行中:
最棘手的案子。在这里使用for-each循环和迭代器不起作用(正如您所注意到的;-)。您必须自己跟踪更改。
一个可行的解决方案是将侦听器存储在ArrayList
中,并使用标准for循环遍历该列表:
for (int i =0; i < listeners.size(); i++) {
Listener l = listeners.get(i);
if (l == null)
continue;
l.handleEvent();
}
for(inti=0;i
删除侦听器会将数组中的元素设置为null。
新的侦听器被添加到末尾,因此将在当前执行中运行。
请注意,此解决方案只是一个示例,不是线程安全的!
有时可能需要一些维护来删除null
元素,以防止列表变得太大
你自己决定需要什么
我个人最喜欢的是第二个。它允许在执行时进行修改,但不会更改当前运行的行为,这可能会导致意外的结果。请发布一个更详细的问题,其中包含更大的代码片段,以澄清问题。侦听器不是某种集合(如列表)吗?如果是这样的话,也许我没有回答这个问题,但一般来说
for (int i =0; i < listeners.size(); i++) {
Listener l = listeners.get(i);
if (l == null)
continue;
l.handleEvent();
}