【问题标题】:Monitor changes to a collection监视对集合的更改
【发布时间】:2009-02-16 11:49:18
【问题描述】:

假设你有以下 java bean:

public class MyBean
{
    private List<String> names = new ArrayList<String>();

    public void addName(String name)
    {
        names.add(name);
        fireNamesPropertyChange(name);
    }
}

您通常如何为集合实现属性更改事件?您是否尝试使用似乎更适用于数组而不是集合的 index 属性?

【问题讨论】:

    标签: java observer-pattern javabeans


    【解决方案1】:

    注意:我在意识到自己的一些错误后更新了这篇文章,所以这不是原来的,而是更精致的)

    为此,我将创建两个新接口,ListListenerListenable,然后我将创建一个类似 ListenableArrayList 的新类,它将包装每个 List 方法并调用一个(或多个) ListListener中定义的相关方法。在代码中它会是这样的:

    public class ListenableArrayList<T> extends ArrayList<T>
                                        implements Listenable<T> {
    
        private ArrayList<T> internalList;
        private ListListener<T> listener;
    
        /* .. */
    
        public void add(T item) {
            listener.beforeAdd(T item);
            internalList.add(item);
            listener.afterAdd(T item);
        }
    
        /* .. */
    
        public void setListener(ListListener<T> listener) {
            this.listener = listener;
        }
    
    }
    
    public interface ListListener<T> {
        /* .. */
        void beforeAdd(T item);
        void afterAdd(T item);
        /* .. */
    }
    
    public interface Listenable<T> {
        /* .. */
        void setListener(ListListener<T> listener);
        /* .. */
    }
    

    我这样做的原因是允许动态创建真正的临时侦听器,而不是将 ListenableArrayList 绑定到某些特定实现。例如,以下是可能的:

    Listenable<String> list = new ListenableArrayList<String>();
    
    list.setListener(new ListListener<String>() {
        @Override
        public void beforeAdd(String item) {
            System.out.println("About to add element "+item+"...");
        }
        @Override
        public void afterAdd(String item) {
            System.out.println("...element "+item+" has been added.");
        }
    });
    

    可能有点混乱,但另一方面,这可以很容易地扩展到 Collections、Sets 等等。

    【讨论】:

    • ...你当然也可以直接调用监听器方法,我只是想用这个例子尽可能的清楚。
    • List list = new ListenableArrayList(); ((Listenable)list).setListener(new ListListener() { ? Icky!
    • 可怕的是,这是您唯一抱怨的事情,因为我刚刚注意到另一个更重要的问题。我会在一分钟内更新帖子。
    • 哦,至于匿名内部类,它是创建原型的好方法。最终我会从那里重构它,但第一次制作漂亮的代码不是我的首要任务,我希望首先正确地表现代码。
    • @Esko,你能告诉我你为什么扩展 ArrayList吗?由于您使用 internal 列表来保存 added 项目,因此整个班级一团糟。您的“实现”包含两个不同的项目数组,如果他们没有正确地观察它,这样一个类的用户会感到惊讶.. // 我不小心投票了 ,(
    【解决方案2】:

    看看Glazed Lists 库,它支持可观察的集合。

    如果我自己做,我可能会使用 elementsAdded、elementsRemoved 方法或类似方法创建自定义 Listener 接口 :-)(也取决于我的需要)

    【讨论】:

    • 我通常也是这样做的。只是好奇其他人都在做什么。至少我得到了一些不错的 Collection 链接,这些链接实际上是 oberserable。 :)
    • AndreyChaschev 的回答不需要任何外部库(但需要 Java7)
    • @Benj:毫不奇怪 8 年前的答案不再重要 ;-)
    • @PeterŠtibraný 当然,在我每天使用 Java 6 到 8 时分享我的想法;-)
    【解决方案3】:
    【解决方案4】:

    通常我会执行以下操作:

    public class MyBean {
        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
        private List<String> names = new ArrayList<String>();
        public void addName(String name) {
            names.add(name);
            pcs.firePropertyChange("names", null, Collections.unmodifiableList(names));
        }
        public void addPropertyChangeListener(PropertyChangeListener l) {
            pcs.addPropertyChangeListener(l);
        }
        public void removePropertyChangeListener(PropertyChangeListener l) {
            pcs.removePropertyChangeListener(l);
        }
    }
    

    PropertyChangeSupport 代表您管理侦听器并触发事件。

    通过将 null 作为“旧值”传递,它会强制触发事件。 (很可能听众无论如何都不会真正关心旧值)

    【讨论】:

    • 我对索引属性如此着迷,以至于我什至没有想到这一点。当您并不真正关心是否添加/删除了某个对象时,这很好用。
    • @willcodejavaforfood 这是一百万年后的事,但如果您关心对象是被添加还是被移除,您可以更改PropertyChangeEvent 的 id。
    • @sdasdadas - 哇 - 不要那样做! PropertyChangeEvent 中的名称必须 与实际的属性名称匹配(与 get/set 方法的名称相同 - “get”/“set”,并且首字母小写。)从不将其设置为“添加名称”或“删除名称”之类的内容。否则你会破坏任何理解 JavaBeans 工作原理的框架或人......
    • @ScottStanchfield 很高兴知道!
    【解决方案5】:

    JDK 7+ 解决方案:

    import javafx.collections.*;
    import java.util.*;
    
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            list.add("s1");
            list.add("s2");
    
            ObservableList<String> observableList = FXCollections.observableList(list);
            observableList.addListener(new ListChangeListener<String>() {
                @Override
                public void onChanged(Change<? extends String> change) {
                    while(change.next()){
                        System.out.println("added: " + change.getAddedSubList());
                    }
                }
            });
    
            observableList.add("s3");
        }
    }
    

    【讨论】:

    • 这是javafx解决方案
    • 这有什么问题?它与 UI 部分隔离
    【解决方案6】:

    对于摇摆 GUI 事件,我通常只使用 EventListenerList 为我完成工作。

    编辑:关于问题的改写:你如何对待集合,我通常会使用类似于集合类型的事件,因此例如 TreeModel 事件通常需要一个 TreePath 参数,或者我在地图中的某些东西'd 指示键。

    然而,对于简单的 JavaBean,最常见的是假设一个列表/数组并只使用索引。

    【讨论】:

    • 我对监听器的存储方式不感兴趣。我正在寻找的是可以处理集合的 PropertyChange 的替代方案。
    • 并注意这不是 Swing 特定的
    【解决方案7】:

    我认为你需要fireNamesPropertyAddfireNamesProperyDelete。恕我直言,列表级通知将不起作用,即使它是一个数组并且添加了一个索引,因为它无法处理删除。如果某个索引处的元素可以更改,则还需要fireNamesProperyChange。除了字符串值之外,将索引作为参数可能会很有用。

    【讨论】:

      【解决方案8】:

      您是否正在寻找java.beans.PropertyChangeSupport

      在我看来,你应该避免PropertyChangeEventIndexedPropertyChangeEvent 更糟,而且 Swing 很少使用。最好缩小类型的焦点,并触发javax.swing.event.ChangeEvent 或类似的(甚至只是调用Runnable)。

      对于某些类型(如列表!),Peter Štibraný 在另一篇文章中提到的 Glazed Lists(或等效的)似乎是一个不错的选择。

      【讨论】:

      • 我正在寻找它的替代品 :)
      猜你喜欢
      • 1970-01-01
      • 2020-01-20
      • 1970-01-01
      • 1970-01-01
      • 2012-05-02
      • 2021-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多