如何检测列表是否已更改?
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/
我在 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/