Почему EventListenerList обходится в fireFooXXX() в обратном направлении?
Я не понимаю смысл этого кода, взятого из документации javax.swing.event.EventListenerList:
protected void fireFooXXX() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==FooListener.class) {
// Lazily create the event:
if (fooEvent == null)
fooEvent = new FooEvent(this);
((FooListener)listeners[i+1]).fooXXX(fooEvent);
}
}
}
- Почему список перемещается назад?
- Почему только каждый второй слушатель называется?
Запуск событий реализован именно таким образом в javax.swing.tree.DefaultTreeModel среди других, так что, очевидно, я просто не понимаю, что именно.
3 ответа
- Вероятно, из соображений производительности: итерация в обратном направлении быстрее, потому что сравнение с 0 - это одиночная инструкция машинного кода - многие бывшие программисты на С уже укоренились в ней, хотя в наши дни это не имеет значения. Обратите внимание, что нет никакой гарантии относительно порядка, в котором слушатели будут уведомлены так или иначе.
- Посмотрите на остальную часть класса - он хранит типы слушателей, чтобы обеспечить безопасность типов.
1.
Одна из возможных проблем при обходе слушателей описана в Swing Hacks № 94 и возникает, если один из них удаляет себя как слушателя в реализации fooXXX().
Рассмотрим этот слушатель, который может удалить себя после получения события:
public class FooListener implements EventListener {
private int i;
public FooListener(int i) {
this.i = i;
}
public fooXXX(FooEvent foo) {
System.out.println(i);
if (i == 1) {
((FooEventSource)foo.getSource()).removeListener(this);
}
}
}
и эта реализация обхода слушателя:
public void fireFooXXX() {
for (int i=0; i<listeners.size(); i++) {
// Lazily create the event:
if (fooEvent == null)
fooEvent = new FooEvent(this);
listeners.get(i).fooXXX(fooEvent);
}
}
Теперь предположим, что мы создали ряд таких слушателей:
fooEventSource.addListener(new FooListener(0));
fooEventSource.addListener(new FooListener(1));
fooEventSource.addListener(new FooListener(2));
fooEventSource.addListener(new FooListener(3));
Запуск события даст следующий результат:
0
1
3
Мы бы зациклились на слушателях по индексу от 0 до 3. При индексе 1 слушатель удаляет себя из внутреннего массива слушателей, в результате чего слушатели 2 и 3 смещаются вниз к индексу 1 и 2. Цикл продолжается с индекса 2 который теперь содержит слушателя 3. Слушатель 2 был пропущен.
Итерация в обратном направлении устраняет эту проблему, поскольку удаление слушателя только сместит индекс прослушивателей, которые уже были вызваны.
Но
EventListenerList не имеет этой проблемы, поскольку методы add() и remove() являются копируемыми при записи, а обход слушателя в предлагаемом использовании работает с экземпляром списка слушателей, возвращаемым getListenerList() перед циклом.
Еще несколько обсуждений об этом можно найти в этой теме, где причины, по-видимому, сводятся к одному из:
спектакль
порядок событий (последний добавленный слушатель будет первым, кто будет уведомлен)
2.
akf и Michael Borgwardt уже ответили, что EvenListenerList хранит типы слушателей в дополнение к слушателям. Я предполагаю, что причина этого в том, что он позволяет одному EventListenerList обрабатывать слушатели разных типов.
Ответить #2: Каждый второй слушатель вызывается, потому что массив, который использует EventListenerList, устанавливается как массив пар Listener-Type, Listener-Instance.