Подходящий класс коллекции для слушателей событий в Java - PullRequest
13 голосов
/ 15 января 2010

Связанный: Имеет ли java структуру данных "LinkedConcurrentHashMap"?


Я ищу класс коллекции для хранения ссылок на прослушиватели событий.

В идеале я хотел бы, чтобы коллекция имела следующие свойства (в порядке приоритета):

  1. Поддерживает порядок вставки. Более ранние слушатели могут отменить событие, не позволяя доставить его слушателям, добавленным позже. Это сломается, если использовать класс, такой как HashSet, чей итератор может возвращать элементы в неправильном порядке.
  2. Использует WeakReference s, чтобы список слушателей не препятствовал сборщикам мусора.
  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. Существует ли такой класс в библиотеке где-нибудь?

Ответы [ 5 ]

7 голосов
/ 18 января 2010

Вы можете использовать WeakListeners (см. 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 of source.

7 голосов
/ 18 января 2010

Я начну с того, что у вас есть пара требований, которые не имеют смысла вместе. Вы ищете коллекцию, которая удаляет дубликаты и поддерживает слабые ссылки, которые указывают мне, что слушатели могут появляться и исчезать в неопределенное время. Однако вы хотите сохранить порядок вставки и позволить одному слушателю отменить все последующие уведомления. Для меня это звучит как рецепт труднодоступных ошибок, и я настоятельно рекомендую переосмыслить его.

Тем не менее, у вас есть одно требование, которое в значительной степени определяет решение: вам не нужен 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

Вы, наверное, сейчас беситесь, говоря: «Список! Это линейная структура данных! Я не могу это использовать, вставка - O (N)!»

Ну, да, вы можете. Я не знаю, сколько слушателей вы планируете иметь. Но пока вы <100 (и, скорее всего, <100 000), стоимость линейного поиска для вставки и удаления не будет иметь значения. </p>

Гораздо интереснее с точки зрения кодирования то, как вы справляетесь со слабой ссылкой. Вы заметите, что я явно разыменовываю его в переменную перед проверкой референта на ноль. Это критически важный код при работе со ссылочными объектами: хотя крайне маловероятно, что референт будет собран между двумя вызовами get(), это возможно.

Что подводит меня к самому WeakReference. Вам нужно будет создать свой собственный подкласс, который переопределяет методы equals() и hashCode() для делегирования его референту. Я думал, что у меня как раз такой класс валяется, но, видимо, нет, так что оставлю это на ваше усмотрение.

1 голос
/ 27 апреля 2015

Набор - это подходящая коллекция для использования со слушателями.

Если вы полагаетесь на порядок вставки слушателей, ваш дизайн нарушен. Это не позволяет слушателям быть ИЗОЛИРОВАННЫМИ и НЕЗАВИСИМЫМИ от других слушателей. Используйте наборы вместо списков.

Если вы полагаетесь на WeakReferences, ваш дизайн не работает. Удалить слушателей в том же объекте, где вы его добавили. Эта симметрия поддерживает READABILITY и MAINTAINABILITY. Устранение ошибок программирования забытых отписок слушателей со слабыми ссылками только скрывает проблему.

Если вы предоставляете свою коллекцию слушателей другим объектам, а не наблюдаемому объекту, тогда ваш дизайн нарушается. Держите набор закрытым для поддержки ENCAPSULATION.

Если вы переопределяете equals и hashcode ваших слушателей, ваш дизайн не работает. Это скрывает проблему ненужных вызовов функций. Предотвратите ненужные звонки вместо этого. В конце концов, избыточное равенство и хэш-код слушателей не являются необходимыми.

В средах MULTITHREADING помещайте МОНИТОР в «слушатели» ресурса, добавляя, удаляя или повторяя его. Вы можете создать DEFENSIVE COPY перед итерацией, чтобы избежать исключения ConcurrentModificationException. Тогда итерация не должна быть СИНХРОНИЗИРОВАНА, но действие копирования должно быть.

Любое другое требование должно быть адаптировано или переформулировано в соответствии с этими утверждениями. Любая другая практика приведет к неработоспособному коду, утечкам памяти из-за отсутствия изоляции, независимости, инкапсуляции и ясности.

0 голосов
/ 18 января 2010

Вы можете расширить WeakReference, чтобы переопределить equals и hashcode, затем вы можете использовать их в LinkedHashSet.

0 голосов
/ 15 января 2010

Вы можете заключить каждую ссылку слушателя в WeakReference , а затем использовать CopyOnWriteArraySet .

...