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

How to detect if a list is changed?

提问人:diziaq 提问时间:6/19/2019 最后编辑:diziaq 更新时间:6/19/2019 访问量:10918

问:

我有一个由鲜为人知的专有框架管理的类中的字段。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

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

爪哇岛 散 列 可变

评论

2赞 J_D 6/19/2019
也许可以考虑观察者模式
1赞 Thilo 6/19/2019
您鲜为人知的专有框架也可以用于 setter 方法吗?然后,您可以在那里放置一些代码来通知更改观察者。@BindMagic
0赞 Thilo 6/19/2019
或者,如果“延迟检测”足够好,你自己的代码可以通过 getter 访问唯一代码,并且该 getter 可以跟踪引用(并在必要时通知观察者)。与在集合迭代器上检测 ConcurrentModificationException 的方式类似。valueslastSeenValue
1赞 Bohemian 6/19/2019
Map 值实例是否可变?Object
1赞 Thilo 6/19/2019
@diziaq 但是你可以做一个二传手方法,不是吗?或者你是说你根本无法更新这个类?

答:

0赞 The_Cute_Hedgehog 6/19/2019 #1

我会尝试使用对象。下面是一个类示例。您可以对存储在列表中的对象应用相同的操作。PropertyChangeListenerSharedEntity

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);
    }
}

评论

0赞 diziaq 6/19/2019
此解决方案不处理列表元素更改的情况。至少当从列表中添加或删除某些内容时。
0赞 The_Cute_Hedgehog 6/19/2019
它说“您可以对存储在列表中的对象应用相同的内容。这是一种非常简单的侦听对象属性更改的方法。
1赞 TreffnonX 6/19/2019 #2

问题可能出在访问列表的线程上。它很可能不应该被某种侦听器解析所困扰,这就是为什么没有将侦听器附加到列表的正确方法。

但是,如果您可以控制 SharedEntiry 类,则可以使用 synchronized “入侵”列表的访问权限。但是您明确表示,可以重新创建列表,因此我认为存储在后面的实例实际上可以替换。values

基本上你有三种情况:

1 -List 被新 List 替换:values

通过在 List 上进行第二次引用来解决这个问题:

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

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

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

是的,您仍然需要定期检查,但身份比较相对便宜,因此可以在后台运行一个负担得起的线程。

2 -List 将替换为新列表(未设置的类型):values

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

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

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

然后,该侦听器仅设置一个“脏”标志,并且您的方法知道发生了更改(并重置该标志)。

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

评论

0赞 diziaq 6/19/2019
是的,可观察的列表将是完美的。问题在于,框架可以将任何实现 List 接口的实例设置为字段。
0赞 TreffnonX 6/19/2019
完善。如果发生这种情况,您知道发生了更改;) - 然后只需将列表替换为原始列表类型并传输内容即可。下次访问它时,它将被替换为框架的 List-Type,并且您再次知道发生了更改。
1赞 Bohemian 6/19/2019 #3

使用哈希值不是确定的,因为相同的哈希值可以从不同的输入中产生,尽管几率很小。

“被改变”和“与众不同”意味着不同的东西。考虑其中一个映射中的条目,该条目在调用方法之间从 to 更改,然后返回 again - 它已更改,但并无不同。我假设你的意思是“不同”。"A" -> 1"A" -> 2"A" -> 1

检查时制作副本并将其与当前状态进行比较。假设映射值是不可变的:

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 值是(或包含)可变对象,则在创建副本时必须对它们进行深层复制。

仅供参考,如果两个参数都为 null,则返回 Objects#equals()。true

0赞 Akshay Bande 6/19/2019 #4

您可以使用 Observer Pattern 来检测 中的更改。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/