在 Java 中定义自定义源-事件监听器






4.87/5 (10投票s)
事件驱动编程模型
* 注意:已更新新源代码(SpeedEvent 不需要扩展 EventObject,我的错!)
背景
* 如果您已经熟悉标准的 Java GUI 事件监听器对,可以跳过此部分,直接查看 定义自定义源、事件和监听器。
在 Java(尤其是 GUI)程序中,用户可能会与按钮、复选框、汽车或银行账户进行交互。程序决定是忽略还是响应这些情况。例如,如果我们想在用户单击按钮时执行某些操作,那么我们需要确定三件事:**(1)** 按钮本身,**(2)** 按钮被单击时的事件,以及 **(3)** 对按钮单击事件感兴趣的响应代码(称为“监听器”)。
在 Java 中,按钮被单击时的事件称为 **ActionEvent**。当这种事件发生时,对该 ActionEvent 感兴趣的监听器(称为 **ActionListener**)会包含一个要调用的方法(称为“处理程序”)。同样,Java 预先将此处理程序方法定义为 **actionPerformed(ActionEvent e)**。这是可能的,因为 ActionListener 已经被按钮“注册”以“监听”ActionEvent。
Java 中的事件驱动编程模型包含三种类型的对象
- 源代码
- 事件
- 监听器
源对象(例如,JCheckbox)在用户选中或取消选中复选框时会触发一个事件对象(例如,ItemEvent 类型)。
事件对象包含有关其源对象的信息。
监听器对象是一个接口,必须由源对象注册为“监听器”,以便在事件对象被触发时能够响应,并调用监听器对象的方法处理程序。
**注意**:一个源对象可以触发一个或多个事件,一个源对象可以注册一个或多个监听器。此外,一个监听器可以声明一个或多个处理程序。例如,Java 已经提供了标准模型
- 当用户选中或取消选中复选框时,**JCheckBox** 可以触发 **ActionEvent** 和 **ItemEvent**。相应的监听器接口是 **ActionListener**(包含处理程序方法 **actionPerformed(ActionEvent e)**),以及 **ItemListener**(包含处理程序 **itemStateChanged(ItemEvent e)**)。
- 当用户在组件上按下、单击、移动、移出鼠标,或在该组件上按下或键入键盘按键时,组件(JTextArea 或 JLabel)可以触发 **MouseEvent** 或 **KeyEvent**。相应的监听器接口是 **MouseLister**(定义了多个处理程序,如 **mousePressed(MouseEvent e)** 和 **mouseEntered(MouseEvent e)**),以及 **KeyListener**(包含预定义的处理程序)。
自定义源、事件和监听器
Java 已经提供了许多带有事件监听器对的标准组件。但我们希望定义自己的,因为它更适合我们的应用程序。例如,当客户从银行账户中提取过多金额时,银行账户可以触发自定义事件 **BalanceEvent**。为了让我们的自定义 **BalanceListener** 的处理程序 **balanceViolated(BalanceEvent e)** 做出适当响应,它应该能够知道当前余额是多少、账户持有人需要维持的最低金额,以及该尝试交易的日期和时间。这意味着 BalanceEvent 包含了处理程序所需的数据。这在使用预定义的 Java 事件监听器对时是无法实现的。
在定义自己的事件监听器对时,我们应该严格遵循命名约定
**XEvent - XListener**(其中 X 可以是 'Action'、'Mouse'、'Account'、'Balance'、'Speed' 等)
自定义 XEvent
class XEvent { // extends java.util.EventObject
// Data that are associated with the source object
String customer;
int balance;
Date dateWidthraw;
// an optional object argument, representing the source object
// Other optional arguments are information related to the source object
public XEvent(/*Object source, */ String customer, int balance, Date date,...) {
// super(source);
// code to set the associated data here
}
}
**注意**:自定义 XEvent 的构造函数:这是源对象将数据传递给触发事件的方式。
自定义 XListener
interface XListener { // extends java.util.EventListener
// It's up to the client code to decide how to implement the handler(s)
void handlerMethod1(XEvent e);
void handlerMethod2(XEvent e);
}
源代码
class Source {
private XListener listener;
// or more flexible, the Source should maintain a list (collection) of XListeners
// The Source must have code to register/de-register the XListener
void addXListener(XListener myListener)
// code to add myListener to the list of XListeners
// the same for removeXListener method
// When a condition happens, source object fires the event
XEvent myEvent = new XEvent(this, param1, param2,...)
// 'this' is the source object that fires myEvent
// param1, param2,... are date describe the status, what is going on to the event
listener.handlerMethod(myEvent) // invoke the handler(s)
}
正如我们所见,源对象可以维护一个监听器列表。当发生某种情况时(例如银行账户余额不足,或某个生物学课程已满无法再招收学生),源对象会调用其监听器上的处理程序方法。由于我们将 XEvent 对象传递给这些处理程序,因此处理程序可以做出适当响应。例如,通知客户端代码当前的余额、尝试提取的金额以及日期。
客户端代码(带 main 方法)
// The client code that makes use of custom Source, XEvent-XListener pair
public class Program {
// Create a source object that can fire an XEvent
// source object can be a bank account, a course in college, or a car
Source source = new Source();
XListener listener = new ClassThatImplementsXListener();
// listener(s) interested in the XEvent, and contains the handler(s)
// to be invoked when such event occurs
// ClassThatImplementsXListener is an 'inner' class of the class Program
// to implement the handler(s) in a custom way based on our need
source.addXListener(listener);
// Without this registering, nothing will happend!!
// method on the source object here that causes source to fire the event
// for example:
source.widthDraw(9000); // for bank account object or
source.speedUp(100); // for car object
// After this method, the source delegates the event fired
// to the listener for processing (invoking the handle(s))
// Inner class
private class ClassThatImplementsXListener implements XListener {
@Override handler(s)
void handlerMethod1(XEvent e) {
// code
}
void handlerMethod2(XEvent e) {
// code
}
}
}
示例
现在,我们正在模拟一个简单的汽车对象,它可以触发 **SpeedEvent**。监听器是 **SpeedListener** 接口。最高速度暂时设置为 60MPH,最低速度 40,默认值为 50,当汽车在高速公路上行驶时。
当汽车行驶速度过快或过慢时,它会触发 SpeedEvent。SpeedEvent 定义如下:
public class SpeedEvent {
private int maxSpeed;
private int minSpeed;
private int currentSpeed;
public SpeedEvent(/*Object source, */ int maxSpeed, int minSpeed, int currentSpeed) {
// super(source);
this.maxSpeed = maxSpeed;
this.minSpeed = minSpeed;
this.currentSpeed = currentSpeed;
}
// and some getters here
}
相应的 **SpeedListener** 声明了 2 个处理程序
public interface SpeedListener {
public void speedExceeded(SpeedEvent e);
public void speedGoneBelow(SpeedEvent e);
}
回到 Car,它维护一个 SpeedListeners 列表
private ArrayList speedListenerList = new ArrayList();
SpeedListener 的注册方法
// Register an event listener
public synchronized void addSpeedListener(SpeedListener listener) {
if (!speedListenerList.contains(listener)) {
speedListenerList.add(listener);
}
}
当汽车加速时
public void speedUp(int increment) {
this.currentSpeed += increment;
if (this.currentSpeed > this.maxSpeed) {
// fire SpeedEvent
processSpeedEvent(new SpeedEvent(this.maxSpeed, this.minSpeed, this.currentSpeed));
}
}
我们看到,当当前速度超过最高速度时,会创建一个 EventSpeed 对象,其中包含相关信息:源、最高速度和当前速度。然后触发该 EventSpeed 对象
private void processSpeedEvent(SpeedEvent speedEvent) {
ArrayList tempSpeedListenerList;
synchronized (this) {
if (speedListenerList.size() == 0)
return;
tempSpeedListenerList = (ArrayList) speedListenerList.clone();
}
for (SpeedListener listener : tempSpeedListenerList) {
listener.speedExceeded(speedEvent);
listener.speedGoneBelow(speedEvent);
}
}
这个 **processSpeedEvent(SpeedEvent e)** 方法将在 SpeedEvent 被触发时执行。它会调用 speedListenerList 中每个 SpeedListener 对象上的每个处理程序,以通知并处理该事件。
我们已经创建并触发了事件,并将处理委托给了监听器。由客户端代码来实现具体的处理程序。
**注意**:使用“synchronized”和“clone”是因为在 processSpeedEvent() 方法运行时,可能会向 speedListenerList 添加新的监听器,或者从 speedListenerList 中删除当前监听器。这会导致 speedListenerList 损坏。
似乎一切都完成了。现在是客户端代码(带 main 方法)来利用上述自定义 Car 和 SpeedEvent-SpeedListener 对。
public static void main(String[] args) {
Car myCar = new Car(60, 40, 50);
SpeedListener listener = new MySpeedListener();
myCar.addSpeedListener(listener);
// Add more listeners if you want
myCar.speedUp(50); // fires SpeedEvent
myCar.speedUp(50); // fires SpeedEvent
myCar.slowDown(70);
myCar.slowDown(70); // fires SpeedEvent
}
内部类 MySpeedListener 定义了自定义的具体处理程序如下:
// Inner class
private static class MySpeedListener implements SpeedListener {
@Override
public void speedExceeded(SpeedEvent e) {
if (e.getCurrentSpeed() > e.getMaxSpeed()) {
System.out.println("Alert! You have exceeded " + (e.getCurrentSpeed() - e.getMaxSpeed() + " MPH!"));
}
}
@Override
public void speedGoneBelow(SpeedEvent e) {
if (e.getCurrentSpeed() < e.getMinSpeed()) {
System.out.println("Uhm... you are driving " + e.getCurrentSpeed() + " MPH. Speed up!");
}
}
}
或者,如果您不想使用内部类,也可以使用匿名类代替
public static void main(String[] args) {
Car myCar = new Car(60, 40, 50);
SpeedListener listener = new MySpeedListener();
myCar.addSpeedListener(listener);
// Add more listeners if you want
// Anonymous inner class
myCar.addSpeedListener(new SpeedListener() {
@Override
public void speedExceeded(SpeedEvent e) {
// Code
}
@Override
public void speedGoneBelow(SpeedEvent e) {
// Code
}
});
myCar.speedUp(50); // fires SpeedEvent
myCar.speedUp(50); // fires SpeedEvent
myCar.slowDown(70);
myCar.slowDown(70); // fires SpeedEvent
}
结果输出将是:
Alert! You have exceeded 40 MPH!
Alert! You have exceeded 90 MPH!
Uhm... you are driving 10 MPH. Speed up!