Есть ли реализация карты с слушателями для Java?

Мне нужна реализация Map, в которой я мог бы добавить слушателей для событий put().

Есть ли что-нибудь подобное в стандарте или каких-либо сторонних библиотеках?

Ответ 1

Я не знаю ни стандартного, ни третьего лица, но это просто, просто создайте класс, который обертывает другую карту и реализует интерфейс карты:

public class MapListener<K, V> implements Map<K, V> {

    private final Map<K, V> delegatee;

    public MapListener(Map<K, V> delegatee) {
        this.delegatee = delegatee;
    }

    // implement all Map methods, with callbacks you need.

}

Ответ 2

Сезон по вкусу. Это является представительным, а не нормативным. Конечно, у него есть проблемы.

public class ListenerMap extends HashMap {

    public static final String PROP_PUT = "put";
    private PropertyChangeSupport propertySupport;

    public ListenerMap() {
        super();
        propertySupport = new PropertyChangeSupport(this);
    }

    public String getSampleProperty() {
        return sampleProperty;
    }

    @Override
    public Object put(Object k, Object v) {
        Object old = super.put(k, v);
        propertySupport.firePropertyChange(PROP_PUT, old, v);
        return old;
    }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.removePropertyChangeListener(listener);
    }
}

Ответ 3

То, что вы по существу запрашиваете, - это кэш, который может предоставлять уведомление о событиях. Есть некоторые продукты, такие как Infinispan, которые уже предоставляют это для вас, но не зная, что ваш вариант использования трудно рекомендовать.

Если вы хотите простой ObservableMap, его следует легко реализовать. Вам просто нужно создать шаблон Observer. Вы можете найти пример здесь.

Ответ 4

Вот рабочий пример карты, которая запускает события изменения свойств при поставке и удалении. Реализация разделена на два класса:

ListenerModel

Содержит методы, связанные с добавлением и удалением прослушивателей изменений, а также метод для изменения свойств.

ListenerMap

Расширяет ListenerModel и реализует интерфейс java.util.Map путем делегирования. Он запускает изменения свойств только в методе put и remove. Было бы целесообразно запускать свойства по другим методам, например, например. clear(), putAll().

ListenerModel

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class ListenerModel {

    private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }

    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        changeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }
}

ListenerMap

import java.util.*;

public class ListenerMap<K, V> extends ListenerModel implements Map<K, V> {

    public static final String PROP_PUT = "put";

    public static final String REMOVE_PUT = "remove";

    private Map<K, V> delegate = new LinkedHashMap<>();

    @Override
    public void clear() {
        delegate.clear();
    }

    @Override
    public boolean containsKey(Object key) {
        return delegate.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return delegate.containsValue(value);
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return delegate.entrySet();
    }

    @Override
    public V get(Object key) {
        return delegate.get(key);
    }

    @Override
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    @Override
    public Set<K> keySet() {
        return delegate.keySet();
    }

    @Override
    public V put(K key, V value) {
        V oldValue = delegate.put(key, value);
        firePropertyChange(PROP_PUT, oldValue == null ? null : new AbstractMap.SimpleEntry<>(key, oldValue),
                new AbstractMap.SimpleEntry<>(key, value));
        return oldValue;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        delegate.putAll(m);
    }

    @Override
    public V remove(Object key) {
        V oldValue = delegate.remove(key);
        firePropertyChange(REMOVE_PUT, oldValue == null ? null : new AbstractMap.SimpleEntry<>(key, oldValue),
                null);
        return oldValue;
    }

    @Override
    public int size() {
        return delegate.size();
    }

    @Override
    public Collection<V> values() {
        return delegate.values();
    }
}

Вот тест JUnit 4:

import org.junit.Before;
import org.junit.Test;

import java.beans.PropertyChangeListener;
import java.util.Map;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;

/**
 * Created by Gil on 01/07/2017.
 */
public class ListenerMapTest {

    private ListenerMap<String, String> map;

    @Before
    public void setUp() throws Exception {
        map = new ListenerMap<>();
    }

    @Test
    public void whenPut_ShouldFireTrigger() throws Exception {
        boolean[] fired = {false};
        Map.Entry<String, String>[] listenEntry = new Map.Entry[1];
        boolean[] checkNull = {true};
        PropertyChangeListener propertyChangeListener = evt -> {
            if (ListenerMap.PROP_PUT.equals(evt.getPropertyName())) {
                if(checkNull[0]) {
                    assertThat(evt.getOldValue(), is(nullValue()));
                }
                else {
                    Map.Entry<String, String> oldValue = (Map.Entry<String, String>) evt.getOldValue();
                    assertThat(oldValue.getKey(), is("k1"));
                    assertThat(oldValue.getValue(), is("v1"));
                }
                listenEntry[0] = (Map.Entry<String, String>) evt.getNewValue();
                fired[0] = true;
            }
        };
        map.addPropertyChangeListener(propertyChangeListener);
        map.put("k1", "v1");
        assertThat(fired[0], is(true));
        assertThat(listenEntry[0].getKey(), is("k1"));
        assertThat(listenEntry[0].getValue(), is("v1"));
        checkNull[0] = false;
        map.put("k1", "v2");
    }

    @Test
    public void whenRemove_ShouldNotFire() throws Exception {
        boolean[] fired = {false};
        PropertyChangeListener propertyChangeListener = evt -> {
            fired[0] = true;
        };
        map.addPropertyChangeListener(propertyChangeListener);
        map.put("k1", "v1");
        assertThat(fired[0], is(true));
        fired[0] = false;
        map.removePropertyChangeListener(propertyChangeListener);
        map.put("k2", "v2");
        assertThat(fired[0], is(false));
    }

}