Подтвердить что ты не робот

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

по теме: Есть ли у java "LinkedConcurrentHashMap" структура данных?


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

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

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

4b9b3361

Ответ 1

Вы можете использовать WeakListeners (см. http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/WeakListeners.html) и CopyOnWriteArraySet.

  • Внедрите метод remove(ListenerType listener) в источник событий.
  • В вашем методе register(SomeListener listener) вместо этого добавьте в коллекцию WeakListener:

    listenerCollection.put((ListenerType)WeakListeners.create ( ListenerType.class, listener, this));

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

Ответ 2

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

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

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

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

Ответ 3

A Set - это правильная коллекция для использования с слушателями.

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

Если вы полагаетесь на WeakReferences, ваш дизайн нарушен. Удалите прослушиватели в том же объекте, где вы его добавили. Эта СИММЕТРИЯ поддерживает ЧУВСТВИТЕЛЬНОСТЬ И СОХРАНЕНИЕ. Для устранения ошибок программирования забытых непозволений слушателей со слабыми ссылками только скрывает проблему.

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

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

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

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

Ответ 4

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

Ответ 5

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