有关编写事件侦听器的一般信息

本节讨论在应用程序中实现事件处理程序时要记住的几个设计注意事项。然后,我们向您介绍事件对象,这些对象描述了每个事件。特别是,我们将讨论EventObject,它是所有 AWT 和 Swing 事件的超类。接下来,我们介绍低级事件和语义事件的概念,建议您尽可能选择语义事件。本节的其余部分讨论您可能在某些事件侦听器中使用或在其他人或 GUI 构建器创建的事件侦听器中使用的实现技术。

Design Considerations

关于事件侦听器,要记住的最重要规则是它们应该非常迅速地执行。因为所有绘图和事件侦听方法都在同一线程中执行,所以慢速的事件侦听器方法会使程序显得无响应且重新绘制自身的速度很慢。如果由于事件而需要执行一些冗 Long 的操作,请通过启动另一个线程(或以某种方式向另一个线程发送请求)来执行该操作。有关使用线程的帮助,请参见Swing 中的并发

关于如何实现事件侦听器,您有很多选择。我们不能推荐一种特定的方法,因为一种解决方案并不适合所有情况。但是,即使您在程序中未使用相同的解决方案,我们也可以给您一些提示,并向您展示一些可能会看到的技术。

例如,您可能选择为不同类型的事件侦听器实现单独的类。这可能是易于维护的体系结构,但是许多类也可能意味着性能降低。

在设计程序时,您可能希望在一个不是公共的但更隐蔽的类中实现事件侦听器。私有实现是一种更安全的实现。

如果您有一种非常特定的简单事件侦听器,则可以使用EventHandler类完全避免创建一个类。

获取事件信息:事件对象

每个事件侦听器方法都有一个自变量,该自变量是从EventObject类继承的对象。尽管参数总是从EventObject派生出来,但通常更精确地指定其类型。例如,处理鼠标事件的方法的参数是MouseEvent的实例,其中MouseEventEventObject的间接子类。

EventObject类定义了一种非常有用的方法:

  • Object getSource()

    • 返回引发事件的对象。

请注意,getSource方法返回Object。事件类有时会定义类似于getSource的方法,但返回类型受到更多限制。例如,ComponentEvent类定义了getComponent方法,就像getSource返回引发事件的对象一样。区别在于getComponent始终返回Component。每个事件侦听器的方法指南页面都提到应使用getSource还是其他方法来获取事件源。

通常,事件类定义返回有关事件信息的方法。例如,您可以查询MouseEvent对象,以获取有关事件发生位置,用户单击了多少次,按下了哪些修改键等信息。

概念:低级事件和语义事件

事件可以分为两组:低级事件和语义事件。低级事件表示窗口系统事件或低级 Importing。其他一切都是语义事件。

低级事件的示例包括鼠标和键事件,这两者都是直接由用户 Importing 引起的。语义事件的示例包括动作和项目事件。语义事件可能由用户 Importing 触发;例如,按钮通常会在用户单击时触发一个动作事件,而文本字段会在用户按下 Enter 键时触发一个动作事件。但是,某些语义事件根本不会被低级事件触发。例如,当表模型从数据库接收新数据时,可能会触发表模型事件。

只要有可能,您应该侦听语义事件,而不是低级事件。这样,您可以使代码尽可能健壮和可移植。例如,侦听按钮上的动作事件而不是鼠标事件,意味着当用户try使用键盘替代方式或特定于外观的手势来激活按钮时,按钮将做出适当的反应。在处理诸如组合框之类的复合组件时,必须坚持语义事件,因为您没有可靠的方式在所有可能用于形成复合组件的特定于外观的组件上注册侦听器 Component。

Event Adapters

一些侦听器interface包含多个方法。例如,MouseListenerinterface包含五个方法:mousePressedmouseReleasedmouseEnteredmouseExitedmouseClicked。即使只关心鼠标单击,如果您的类直接实现MouseListener,那么您也必须实现所有五个MouseListener方法。您不关心的那些事件的方法可以有空的主体。这是一个例子:

//An example that implements a listener interface directly.
public class MyClass implements MouseListener {
    ...
        someObject.addMouseListener(this);
    ...
    /* Empty method definition. */
    public void mousePressed(MouseEvent e) {
    }

    /* Empty method definition. */
    public void mouseReleased(MouseEvent e) {
    }

    /* Empty method definition. */
    public void mouseEntered(MouseEvent e) {
    }

    /* Empty method definition. */
    public void mouseExited(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
        ...//Event listener implementation goes here...
    }
}

空方法主体的结果集合会使代码难以阅读和维护。为了帮助您避免实现空的方法主体,API 通常为每个侦听器interface包括一个* adapter *类,该类具有多个方法。 (侦听器 API 表列出了所有侦听器及其适配器。)例如,MouseAdapter类实现了MouseListenerinterface。适配器类实现其所有interface方法的空版本。

要使用适配器,请创建它的子类并仅覆盖感兴趣的方法,而不是直接实现侦听器interface的所有方法。这是修改前面的代码以扩展MouseAdapter的示例。通过扩展MouseAdapter,它继承了MouseListener包含的所有五个方法的空定义。

/*
 * An example of extending an adapter class instead of
 * directly implementing a listener interface.
 */
public class MyClass extends MouseAdapter {
    ... 
        someObject.addMouseListener(this);
    ... 
    public void mouseClicked(MouseEvent e) {
        ...//Event listener implementation goes here...
    }
}

内部类和匿名内部类

如果要使用适配器类,但又不想从适配器类继承公共类怎么办?例如,假设您编写了一个 applet,并且您希望Applet子类包含一些代码来处理鼠标事件。由于 Java 语言不允许多重继承,因此您的类不能同时扩展AppletMouseAdapter类。一种解决方案是在Applet子类内部定义一个* inner class *类,以扩展MouseAdapter类。

内部类对于直接实现一个或多个interface的事件侦听器也很有用。

//An example of using an inner class.
public class MyClass extends Applet {
    ...
        someObject.addMouseListener(new MyAdapter());
    ...
    class MyAdapter extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {
            ...//Event listener implementation goes here...
        }
    }
}

Performance note:

在考虑是否使用内部类时,请记住,应用程序启动时间和内存占用量通常与加载的类数成正比。创建的类越多,程序启动所需的时间就越 Long,并且将占用更多的内存。作为应用程序开发人员,您必须在此与其他设计约束之间取得平衡。我们不建议您将应用程序变成一个单一的类,以期减少启动时间和内存占用,这将导致不必要的麻烦和维护负担。

您可以创建内部类而无需指定名称,该名称称为匿名内部类。乍看起来,它看起来很奇怪,但是匿名内部类可以使您的代码更易于阅读,因为该类是在引用的地方定义的。但是,您需要权衡便利性与增加类数量可能带来的性能影响。

这是使用匿名内部类的示例:

//An example of using an anonymous inner class.
public class MyClass extends Applet {
    ...
        someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                ...//Event listener implementation goes here...
            }
        });
    ...
    }
}

Note:

匿名内部类的一个缺点是 Long 期持久性机制无法看到它们。有关更多信息,请参见JavaBeans跟踪中的JavaBeans™ packageBean Persistence类的 API 文档。

即使事件侦听器需要访问封闭类的私有实例变量,内部类也可以工作。只要您不将内部类声明为static,内部类就可以引用实例变量和方法,就像其代码在包含类中一样。要使局部变量可用于内部类,只需将变量的副本另存为final局部变量。

要引用封闭实例,可以使用EnclosingClass.this。有关内部类的更多信息,请参见Nested Classes

EventHandler 类

EventHandler类支持动态生成简单的单语句事件侦听器。尽管EventHandler仅对某些类型的极其简单的事件侦听器有用,但出于两个原因值得一提。它对:

  • 使事件侦听器具有持久性可以看到但不会阻塞事件侦听器interface和方法的类。

  • 不增加应用程序中定义的类的数量,可以帮助提高性能。

手动创建EventHandler很难。 EventHandler必须仔细构造。如果出错,则不会在编译时通知您,它会在运行时引发晦涩的异常。因此,最好由 GUI 构建器创建EventHandlerEventHandler应该仔细记录。否则,您将冒产生难以阅读的代码的风险。

EventHandler类供交互式工具(例如,应用程序构建器)使用,该工具允许开发人员在 Bean 之间构建连接。通常,连接是从用户interface Bean(事件源)到应用程序逻辑 Bean(目标)构建的。这种最有效的连接将应用程序逻辑与用户interface隔离开。例如,从 JCheckBox 到接受布尔值的方法的连接的EventHandler可以处理提取复选框的状态并将其直接传递给该方法,以便该方法与用户interface层隔离。

内部类是处理用户interface事件的另一种更通用的方法。 EventHandler类仅处理使用内部类可能实现的子集。但是,EventHandler在 Long 期持久性方案上比内部类更好。同样,在实现了多次相同interface的大型应用程序中使用EventHandler可以减少应用程序的磁盘和内存占用。

使用EventHandler的示例EventHandler的最简单用法是安装一个侦听器,该侦听器在不带参数的情况下在目标对象上调用方法。在下面的示例中,我们创建一个 ActionListener,它在javax.swing.JFrame的实例上调用 toFront 方法。

myButton.addActionListener(
        (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));

当按下 myButton 时,将执行语句 frame.toFront()。通过定义 ActionListener interface的新实现并将其实例添加到按钮,可以实现相同的效果,并具有更多的编译时类型安全性:

//Equivalent code using an inner class instead of EventHandler.
    myButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            frame.toFront();
        }
    });

EventHandler的下一个最简单的用法是从侦听器interface中的方法的第一个参数(通常是事件对象)中提取属性值,然后使用它来设置目标对象中的属性值。在下面的示例中,我们创建一个 ActionListener,将目标(myButton)对象的 nextFocusableComponent 属性设置为事件的“ source”属性的值。

EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")

这将对应于以下内部类实现:

//Equivalent code using an inner class instead of EventHandler.
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            myButton.setNextFocusableComponent((Component)e.getSource()); 
        }
    }

也可以创建一个EventHandler,它将传入的事件对象传递给目标的动作。如果第四个EventHandler.create参数为空字符串,则事件将被传递:

EventHandler.create(ActionListener.class, target, "doActionEvent", "")

这将对应于以下内部类实现:

//Equivalent code using an inner class instead of EventHandler.
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            target.doActionEvent(e);
        }
    }

EventHandler的最常见用法是从事件对象的源中提取属性值,并将此值设置为目标对象的属性值。在下面的示例中,我们创建一个 ActionListener,将目标对象的“ label”属性设置为事件源的“ text”属性的值(“ source”属性的值)。

EventHandler.create(ActionListener.class, myButton, "label", "source.text")

这将对应于以下内部类实现:

//Equivalent code using an inner class instead of EventHandler.
    new ActionListener {
        public void actionPerformed(ActionEvent e) {
            myButton.setLabel(((JTextField)e.getSource()).getText()); 
        }
    }