Почему 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);
        }
    }
}
  1. Почему список перемещается назад?
  2. Почему только каждый второй слушатель называется?

Запуск событий реализован именно таким образом в javax.swing.tree.DefaultTreeModel среди других, так что, очевидно, я просто не понимаю, что именно.

3 ответа

Решение
  1. Вероятно, из соображений производительности: итерация в обратном направлении быстрее, потому что сравнение с 0 - это одиночная инструкция машинного кода - многие бывшие программисты на С уже укоренились в ней, хотя в наши дни это не имеет значения. Обратите внимание, что нет никакой гарантии относительно порядка, в котором слушатели будут уведомлены так или иначе.
  2. Посмотрите на остальную часть класса - он хранит типы слушателей, чтобы обеспечить безопасность типов.

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.

Другие вопросы по тегам