如何检测列表是否已更改?

How to detect if a list is changed?

我在 class 中有一个 List 字段,由鲜为人知的专有框架管理。

注释 @BindMagic 由框架管理,因此底层列表有时会发生变化:它可能会被重新创建,或者它的元素可能会发生变化。

class SharedEntity{

  @BindMagic // this annotation does a magic that we cannot control
  private List<Map<String,Object>> values;

  public boolean isChangedSincePreviousCall(){
    // check if "values" have changed since the previous call of this method          
  }
}

我同意这是一个糟糕的设计,但我们假设没有可能影响它。

时不时(不是每个突变)需要检查列表是否已更改。例如,我想用 isChangedSincePreviousCall 方法来做。 也许,像哈希和这样的东西会很好。但我很好奇有没有更好的方法。

检测列表是否更改的最佳做法是什么?

我会尝试使用 PropertyChangeListener 对象。这是 SharedEntity class 的示例。您可以对存储在列表中的对象应用相同的方法。

class SharedEntity {
  private List<Map<String,Object>> values;
  private PropertyChangeSupport pcs = new PropertyChangeSupport();

  public void setValues(List<Map<String,Object>> values) {
   List<Map<String,Object>> oldValues = this.values;
   this.values= values;
   pcs.firePropertyChange("values",oldValues, values); 
  }

  public void addValue(Map<String, Object> value) {
   // store old
   // add new element
   // fire change   
  }

  public void removeValue(Map<String, Object> value) {
   // store old
   // remove value
   // fire change
  }

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

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

问题可能是访问列表的线程。它很可能不应该陷入某种 Listener-resolution,这就是为什么没有适当的方法将侦听器附加到列表的原因。

但是,如果您可以控制 SharedEntiry class,则可以使用同步 'hack' 访问列表。但是你明确指出,可以重新创建列表,所以我假设存储在 values 后面的实例实际上可以被替换。

基本上你有三种情况:

1 values-列表被新列表替换:

通过对列表进行第二次引用来解决此问题:

private List<Map<String,Object>> valuesPrevious;

每当您检查更改时,请先检查列表的身份。如果它们不匹配,您可以确定列表已更改(至少是实例,如果不是内容)。

if (values != valuesPrevious) {
    // handle change.
}

是的,您仍然需要定期检查,但是 identity-comparison 相对便宜,因此可以负担得起 运行 在后台的线程。

2 values-List 被一个新的列表(你没有设置的类型)取代:

如果发生这种情况,将所有值从 API 的列表移动到您的可观察列表的一个实例(如下所述),为该实例设置值并等待下一次更改发生。

3 值已更改,但实例相同:

通过使用 ObservableList(如果您在 Java10+ https://docs.oracle.com/javase/10/docs/api/javafx/collections/ObservableList.html 中实现)或自己实现这样的 List(可能通过扩展现有的 List 类型)来解决这个问题。

然后,该侦听器仅设置一个 'dirty' 标志,并且您的方法知道发生了变化(并重置该标志)。

无论如何,我的建议是确保处理更改的线程仅触发另一个线程来处理更改,而不是锁定访问线程,因为我怀疑您的@BindMagic-API有某种 runtime-relevant 因素(例如,它是某种网络或数据库相关的影子)。 如果您只是锁定线程,直到您处理完您的反应,您可能会出现奇怪的效果、断开连接或最终意外阻塞您正在访问的服务器。

使用散列不是确定的,因为可以从不同的输入生成相同的散列,尽管机会很小。

"Being changed" 和 "being different" 意思不同。考虑其中一个映射中的一个条目,该条目从 "A" -> 1 更改为 "A" -> 2,然后在对您的方法的调用之间再次返回 "A" -> 1 - 它已 changed 不同 。我假设你的意思是 "different".

检查时复制一份,并与当前状态进行比较。假设地图值为 immutable:

class SharedEntity {

    @BindMagic
    private List<Map<String, Object>> values;
    private List<Map<String, Object>> valuesCopy;

    public boolean isChangedSincePreviousCall() {
        newCopy = new ArrayList<>(values);
        boolean result = !Objects.equals(valuesCopy, newCopy);
        valuesCopy = newCopy;
        return result;
    }
}

如果 Map 值是(或包含)可变对象,您必须在创建副本时对它们进行深度复制。

仅供参考 Objects#equals() returns true 如果两个参数都为空。

您可以使用观察者模式来检测 values 中的变化。

您需要创建 Observable。

package com.psl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;

public class MyList extends Observable{

      private List<Map<String,Object>> values;    
    public List<Map<String, Object>> getValues() {
        return values;
    }

    public void setValues(List<Map<String, Object>> values) {

        if(getValues()==null && values!=null){

            setChanged();
            notifyObservers();
        }

        else if( !this.values.equals(values)){
            setChanged();
            notifyObservers();
        }

        this.values = values;
    }

    public static void main(String[] args) {

        MyList myList = new MyList();
        List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
        Notify notify = new Notify();
        myList.addObserver(notify);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("string_value", null);
        myList.setValues(values);                       
    }


}

您必须创建观察者,它将观察 MyList 中的变化

package com.psl;

import java.util.Observable;
import java.util.Observer;

public class Notify implements Observer{

    @Override
    public void update(Observable o, Object arg) {
            System.out.println("List has been changed");                
    }

}

有关可观察模式的更多信息https://springframework.guru/gang-of-four-design-patterns/observer-pattern/