这段代码在JDK中的目的是什么?

3
以下代码摘自Oracle jdk1.8.0_40的AbstractListModel类。
   /**
     * <code>AbstractListModel</code> subclasses must call this method
     * <b>after</b>
     * one or more elements of the list change.  The changed elements
     * are specified by the closed interval index0, index1 -- the endpoints
     * are included.  Note that
     * index0 need not be less than or equal to index1.
     *
     * @param source the <code>ListModel</code> that changed, typically "this"
     * @param index0 one end of the new interval
     * @param index1 the other end of the new interval
     * @see EventListenerList
     * @see DefaultListModel
     */
    protected void fireContentsChanged(Object source, int index0, int index1)
    {
        Object[] listeners = listenerList.getListenerList();
        ListDataEvent e = null;

        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ListDataListener.class) {
                if (e == null) {
                    e = new ListDataEvent(source, ListDataEvent.CONTENTS_CHANGED, index0, index1);
                }
                ((ListDataListener)listeners[i+1]).contentsChanged(e);
            }
        }
    }

我的问题是:
  • 为什么迭代从listeners.length - 2开始,listeners.length - 1元素呢?
  • 为什么事件针对每个其他元素触发 (i -= 2)?
  • 为什么迭代以相反的顺序进行?
此外,这里有一个指向openjdk中代码的链接。

请查看addListDataListener以了解为什么要这样实现。还要注意,侦听器按照它们添加到模型的相反顺序被调用。 - Dakshinamurthy Karra
我猜listeners.length - 2i -= 2的问题是相关的。我现在无法检查代码,但我能想到的直接解释是每次注册侦听器时可能会向listenerList添加两个元素,而只有其中一个应该被通知。 - Chop
2
Javadocе’Ңjavax.swing.event.EventListenerListзҡ„жәҗд»Јз ҒжҸҗдҫӣдәҶе…Ёйқўзҡ„ж–ҮжЎЈпјҢи§ЈйҮҠдәҶдёәд»Җд№ҲиҰҒд»Ҙиҝҷз§Қж–№ејҸдҪҝз”ЁиҜҘзұ»зҡ„е®һдҫӢгҖӮ - Oleg Estekhin
@OlegEstekhin 我只阅读了 getListenerList 的 Javadoc。我以为那里会有答案,但既然没有,我就在这里发帖了。不过另外一件事,getListenerList 的 Javadoc 不应该包含这个信息吗? - Can't Tell
4个回答

6

listeners数组包含监听器的Class对象和监听器实例,它们分别存储在奇数索引和偶数索引中。

因此,循环检查listeners数组中每个偶数索引的类型。

if (listeners[i] == ListDataListener.class

但仅对奇数索引触发事件:

((ListDataListener)listeners[i+1]).contentsChanged(e);
listeners.length - 1没有被跳过。因为当i == listeners.length - 2时,i+1 == listeners.length - 1
我不确定为什么要进行反向迭代。

逆序是因为模型以添加的相反顺序触发事件。 - Dakshinamurthy Karra
3
问题是为什么这个模型会这样做。 - Eran
它必须这样做。如果不按相反的顺序迭代,那么第一个添加的侦听器将首先被触发,这可能不是所需的。Swing中的所有事件触发都是相同的。 - Dakshinamurthy Karra
2
@KDM:那不是一个解释。毕竟,事件通知的顺序是未指定的。我的数据模型都使用Multicaster模式,它根本没有稳定的排序,而Swing并不介意。真正的原因似乎是一种玄学优化,将循环变量与零进行比较被认为比与数组长度的动态值进行比较更有效率。 - Holger
1
@KDM:可消耗事件指的是InputEvent,这些事件由AWT在没有指定顺序的情况下触发。只需查看我之前链接的多路广播器及其工作方式即可了解。最初,它将按插入顺序(而非反向)发送,并且一旦您在中间删除一个监听器,它就会重新排列监听器。InputEvent的文档清楚地说明,消耗意味着更改事件的的行为,而不是其他监听器。在AWT的通知中没有反向插入顺序的证据... - Holger
显示剩余3条评论

2

1
如果你看一下EventListenerList:这个列表由两个元素构成,一个是监听器对象,另一个是对象的类。这就解释了2x2迭代和对类的其他元素的检查。
我觉得这段代码相当丑陋,有很多奇怪的迭代循环重复出现。我不知道这种实现方式的原因,但我认为原因可能是为了保持与遗留JAVA的兼容性——保持API相同,或者可能是为了提高性能,可能是在java 1.5引入泛型后。
为什么要倒序迭代? 我不知道为什么要这样实现。也许是一种实现决策,或者是规范要求先调用最新添加的监听器。 我试图找到另一个好的理由,但我找不到……而且自至少1.6以来,它就一直以逆序迭代(我没有检查老版本)。

1
根据此处显示的添加新侦听器的代码:
public void addListDataListener(ListDataListener l) {
    listenerList.add(ListDataListener.class, l);
}

该列表实际上包含Class实例和对象实例的对。

关于迭代顺序,也许这是有意为之,先通知新的监听器?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接