Is there a Map implementation with listeners for Java?

前端 未结 4 1466
小鲜肉
小鲜肉 2021-02-19 14:01

I would like a Map implementation in which i could add listeners for put() events.

Is there anything like that in the standard or any 3rd party libraries?

相关标签:
4条回答
  • 2021-02-19 14:11

    Season to taste. This is representative, not normative. Of course it has issues.

    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);
        }
    }
    
    0 讨论(0)
  • 2021-02-19 14:20

    I'm not aware of any standard or 3rd party, but it is easy, just create a class which wraps another Map and implements the Map interface:

    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.
    
    }
    
    0 讨论(0)
  • 2021-02-19 14:23

    What you are essentially asking for is a Cache which can provide event notification. There are some products out there like Infinispan that already provide that for you but without knowing your use case its hard to recommend.

    If you want a simple ObservableMap it should be easy to implement. You simply have to create an Observer pattern. You can find an example here.

    0 讨论(0)
  • 2021-02-19 14:33

    Here is a working example of a map which fires property change events on put and remove. The implementation is divided in two classes:

    ListenerModel

    Contains the methods related to adding and removing the change listeners and also a method for firing the property changes.

    ListenerMap

    Extends ListenerModel and implementes the java.util.Map interface by delegation. It fires the property changes in the put and remove method only. It would make sense to fire the properties on other methods like e.g. 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();
        }
    }
    

    Here is a JUnit 4 test:

    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));
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题