我在一个由鲜为人知的专有框架管理的类中有一个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
来做。
可能,像哈希和这样的东西会很好。但是我很好奇还有更好的方法。
检测列表是否更改的最佳实践是什么?
答案 0 :(得分:1)
使用哈希值不是确定的,因为可以从不同的输入产生相同的哈希值,尽管机会很小。
“被改变”和“与众不同”意味着不同的事物。考虑一下其中一个映射中的条目,该条目从"A" -> 1
更改为"A" -> 2
,然后在调用方法之间再次返回"A" -> 1
-已被更改,但没有't 不同。我假设你的意思是“不同”。
检查时制作一个副本,并将其与当前状态进行比较。假设映射值为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值是(或包含)可变对象,则在创建副本时必须对其进行深层复制。
如果两个参数都为空,则FYI Objects#equals()
返回true
。
答案 1 :(得分:0)
我会尝试使用PropertyChangeListener
对象。这是SharedEntity
类的示例。您可以对列表中存储的对象应用相同的方法。
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);
}
}
答案 2 :(得分:0)
问题可能是线程正在访问列表。很有可能不应该以某种侦听器分辨率来解决它,这就是为什么没有适当的方法将侦听器附加到列表的原因。
但是,如果您可以控制SharedEntiry类,则可以使用同步来“侵入”列表的访问权限。但是,您明确指出,可以重新创建该列表,因此我假设存储在values
后面的实例实际上可以被替换。
基本上,您有以下三种情况:
values
-列表已替换为新列表:通过在List上进行第二次引用来解决此问题:
private List<Map<String,Object>> valuesPrevious;
每当您检查更改时,请首先检查列表的身份。如果它们不匹配,则可以确定列表已更改(至少是实例,如果不是内容的话)。
if (values != valuesPrevious) {
// handle change.
}
是的,您仍然需要定期检查,但是身份比较相对便宜,因此可以在后台运行负担得起的线程。
values
-列表被新的列表(您未设置的类型)替换:如果发生这种情况,请将所有值从API列表移至可观察列表的一个实例(如下所述),为该实例设置值,然后等待下一次更改。
使用ObservableList(如果您使用Java10 + https://docs.oracle.com/javase/10/docs/api/javafx/collections/ObservableList.html实现)或自己实现这样的List(可能通过扩展现有的List类型)来解决。
然后,该侦听器仅设置一个“脏”标志,并且您的方法知道发生了更改(并重置了标志)。
无论如何,我的建议是确保处理更改的线程仅触发另一个线程来处理更改,而不是锁定访问线程,因为我怀疑您的@ BindMagic-API具有某种运行时相关因素(例如,它是某物与网络或数据库相关的影子)。 如果只是简单地锁定线程,直到处理完您的反应,您可能会得到怪异的效果,断开连接或最终意外阻塞正在访问的服务器。
答案 3 :(得分:0)
您可以使用观察者模式来检测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/
的更多信息