如何正确地向 Map.Entry 添加值更改侦听器?

How to properly add a value change listener to Map.Entry?

我目前正在进行的一个项目要求我确保在遍历地图条目时,如果调用 Entry.setValue,它将触发值更改事件。我看到我可以尝试在 .put 方法上将侦听器添加到 Map class 的扩展中。我的问题是,正在更改的条目是否会触发地图的 put 方法中的侦听器?或者我会被迫扩展 Map.Entry class 并将侦听器逻辑粘贴到它的 setValue 方法中吗?

如果这个问题很愚蠢,请提前致歉 - 我是第一次以这种方式使用地图,到目前为止我看到的很多信息只会导致扩展地图本身,这似乎最简单但我不知道它是否会涵盖我的情况。

Map.Entry 只是一个接口,因此当然可以编写它的实现来触发值更改事件到注册到它的 EventListener 实例或拥有 Map 个实例。

但我看不出有什么方法可以说服接口 Map 的任何现有实现使用 Entry 的新实现…

所以你最终会创建自己的 Map 实现…

或者,当您需要更改侦听器时 仅在对条目集进行迭代 时,您可以只覆盖 [=13] 的适当实现的方法 Map.entrySet() =] 它将 return 一个 Set 的调整版本——调整的方式是方法 Set.iterator() return 是一个 Iterator 实例,其方法 Iterator.next() return 是您对 Map.Entry 的实现(包装原始的并委托给它的方法......)其 setValue() 方法将触发值更改侦听器。

像这样:

public class MyMapEntry<K,V> implements Map.Entry<K,V>
{
  private final Map.Entry<K,V> m_Wrapped;
  private final ChangeListener m_Listener;
  
  public MyMapEntry( ChangeListener listener, Map.Entry<K,V> entry ) 
  { 
    m_Listener = listener; 
    m_Wrapped = entry; 
  }
  
  @Override
  public void setValue( V value )
  {
    var event = new ChangeEvent( this, getValue(), value );
    m_Wrapped.setValue( value );
    m_Listener.sendEvent( event );
  }

  … // Implement the other methods
}

public class MyIterator<Map.Entry<K,V>> implements Iterator<Map.Entry<K,V>>
{
  private final Iterator<Map.Entry<K,V>> m_Iterator;
  private final ChangeListener m_Listener;

  public MyIterator( final ChangeListener listener, final Iterator<Map.Entry<K,V>> iterator )
  {
    m_Listener = listener;
    m_Iterator = iterator;
  }

  public Map.Entry<K,V> next()
  {
    return new MyMapEntry( m_Listener, m_Iterator.next() );
  }

  … // Delegation for all the other methods 
}

public class MySet<Map.Entry<K,V>> extends HashSet<Map.Entry<K,V>>  
{
  private final ChangeListener m_Listener;

  public MySet( ChangeListener listener, Set<Map.Entry<K,V>> other )
  {
    super( other );
    m_Listener = listener;
  }

  @Override
  public Iterator<Map.Entry<K,V>> iterator()
  {
    return new MyIterator( m_Listener, super.iterator() ); 
  }
}     

public class MyMap<K,V> extends HashMap<K,V>
{
  private final ChangeListener m_Listener;

  public MyMap( ChangeListener listener ) 
  { 
    super();
    m_Listener = listener; 
  }
  
  @Override
  public Set<Map.Entry<K,V>> entrySet()
  {
    return new MySet( listener, super.entrySet() );
  } 
}

这段代码只是一个草图,在运行之前可能需要一些修复!

您复制 MyMap.entrySet() 中的条目集会将您的条目集与原始条目集分离;对地图 结构 的更改将不再反映到您的集合中 – 但对键值和值值的更改仍然会影响您的集合。

在紧要关头,您可以使用 PropertyChangeSupport class。它使管理 propertyChanges 变得非常容易。这里没什么好说的。想要监听变化的各方向地图注册他们的监听器。然后当映射修改值时,支持向所有侦听器触发事件​​。事件 class 中返回的值可能会更改为选择的值。

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;

public class PropertyChangeDemo implements PropertyChangeListener {
    
    public static void main(String[] args) {
        // leave static context of main
        new PropertyChangeDemo().start();
    }
    public void start() {
        MyMap<String,Integer> map = new MyMap<>();
        map.addMapListener(this);
        map.put("B",20);
        map.put("B",99);
        map.put("A",44);
        
        map.entrySet().forEach(System.out::println);
    }

版画

source = map
oldValue = null
newValue = 20
source = map
oldValue = 20
newValue = 99
source = map
oldValue = null
newValue = 44
A=44
B=99

用于演示的监听器。

    public void propertyChange(PropertyChangeEvent pce) {
        System.out.println("source = " + pce.getPropertyName());
        System.out.println("oldValue = " + pce.getOldValue());
        System.out.println("newValue = " + pce.getNewValue());
    }
    
}

修改后class

class MyMap<K,V> extends HashMap<K,V> {
        
    private PropertyChangeSupport ps = new PropertyChangeSupport(this);

    // method to add listener
    public void addMapListener(PropertyChangeListener pcl) {
        ps.addPropertyChangeListener(pcl);
    }
    
    @Override 
     public V put(K key, V value) {
        V ret = super.put(key,value);
        ps.firePropertyChange("map", ret, value);
        return ret;
    }
}   

注意:这个简单的解决方案可能遗漏了一些问题。投入生产使用前应进行测试。首先,有许多不同的方法可以设置 Entry's 值。这仅在 put 被用户或地图本身间接调用时执行。