 | 级别: 初级 John Zukowski (jaz@zukowski.net), 总裁, JZ Ventures, Inc
2003 年 11 月 24 日 许多开发人员为事件处理创建匿名内部类。对于简单的事件处理,内部类可能成为真正的争论话题.幸运的是,Java1.4 引入了
EventHandler 类,它依赖于监听器的动态生成以方便地处理手头的任务。尽管这个新功能通常是让IDE 厂商使用的,但是在本文中,专栏作者 John Zukowski 还是为您展示了如何用它进行手工编码。请参与本文的
讨论论坛,与本文作者和其他读者分享您对本文的看法(也可以单击文章顶部或底部的
讨论访问这个论坛)。
所有 Swing 组件都是 JavaBeans 组件。它们有一系列的 setter 和 getter 方法,这些方法的类似于
void setXXX(类型名) 和
Type getXXX() 。关于这些方法没有什么特别之处,并且正如所预期的,它们遵循
JavaBeans 的属性命名规范。我们今天要讨论的是JavaBeans 组件的一个方面,即一对监听器方法
addXXXListener
(XXXListener name) 和
removeXXXListener (XXXListener name) 。
XXListener
在这里指的是一个监听器对象,它扩展了
EventListener 接口,等候与监听器关联的组件中的各种事件发生。当事件发生时,所有注册的监听器都会得到事件的通知(没有特定的顺序)。通过魔术般的一个小反射(reflection)和一个新的
java.beans.EventHandler 类,您可以将一个监听器附加到一个 bean 上,而无需直接实现这个监听器接口或者创建那些烦人的小匿名内部类。
以前的方法
在深入到使用新的
EventHandler 类的细节之前,让我们回顾一下不使用这个类时是如何进行工作的。我们举一个对
Swing 框架中的按钮选择做出响应的简单例子。选择一个按钮生成一个
ActionEvent 。要对这个事件做出响应,需要将
ActionListener 附加到这个按钮上,如清单 1 所示:
清单 1. 监听标准按钮选择
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonSelection extends JFrame {
public ButtonSelection() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button = new JButton("Pick Me");
Container contentPane = getContentPane();
contentPane.add(button, BorderLayout.CENTER);
button.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Hello, World!");
}
}
);
}
public static void main(String args[]) {
JFrame frame = new ButtonSelection();
frame.setSize(200, 100);
frame.show();
}
} |
这里没有任何神奇之处,您可能已经熟悉这种代码了。这里,
ActionListener 实现是适时定义的,它定义为一个匿名内部类,并直接附加到按钮上。在选择这个按钮时,字符串
Hello, World! 就打印到控制台中。与程序关联的屏幕如图 1 所示:
图 1. 带 ActionListener 的按钮选择
在 JavaBeans 规范中,没有要求您创建匿名内部类进行事件监听。 IDE 工具常常采用这种行为:您说要一个监听器,它就生成一个 stub,然后您填入细节。完成同样工作的其他方式包括在调用类中提供指定的实现或者实现您自己的接口。
定义了每一个实现类后,就会创建一个单独的.class 文件。所以,在前面的
ButtonSelection 程序中,您会看到编译器生成两个
.class 文件:ButtonSelection.class 和 ButtonSelection$1.class。
$1 是
Sun 编译器命名匿名内部类的方式,计数随着每一个类增加。其他编译器可能有不同的命名方式。
用 EventHandler 注册监听器
EventHandler 类提供了另一种将监听器注册到 JavaBeans 组件上的方法。它不是创建一个实现了接口的类、并将这个实现注册到需要监听的事件所在的组件上,而是创建一个
EventHandler 实例并注册它。虽然这个类有一个公共构造函数,但一般不使用它而是使用三个静态
create()
方法,如清单 2 所示:
清单 2. EventHandler 的 create() 方法
public static Object create(Class listenerInterface,
Object target,
String action)
public static Object create(Class listenerInterface,
Object target,
String action,
String eventPropertyName)
public static Object create(Class listenerInterface,
Object target,
String action,
String eventPropertyName,
String listenerMethodName)
|
让我们更详细地看一下这三种方法。
使用 create(Class, Object, String)
因为第一种方法的参数最少,所以它最简单。第一个参数是
EventListener 类型,您要实现的就是它的接口,例如,要响应按钮选择,这个参数应该是
ActionListener.class ,以表示这个接口的
Class 对象。虽然
ActionListener
只有接口中的一个方法,但是以这种方式创建接口的一个实现意味着这个接口实现的所有方法都将执行同样的代码。
第二和第三个参数是相互关联的。它们结合在一起说明调用
Object 目标的
String
操作方法。然后使用反射,您有一个
ActionListener 实现,但是没有在文件系统中增加一个 .class
文件。清单 3 重复了前面
图 1 中的按钮选择例子,使用了一个
EventHandler 。注意
println() 调用需要转移到一个方法中,这样就可以从处理程序中调用它。
清单 3. 展示 create(Class, Object, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class ButtonEventHandler extends JFrame {
public ButtonEventHandler() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JButton button = new JButton("Pick Me");
Container contentPane = getContentPane();
contentPane.add(button, BorderLayout.CENTER);
button.addActionListener(
(ActionListener)EventHandler.create(
ActionListener.class,
this,
"print")
);
}
public void print() {
System.out.println("Hello, World!");
}
public static void main(String args[]) {
JFrame frame = new ButtonEventHandler();
frame.setSize(200, 100);
frame.show();
}
}
|
create() 中调用
EventHandler 的代码只是表示“在需要通知按钮所附的
ActionListener 时,调用我们的
print() 方法(
this )”。不过有一些副作用。第一个是调用需要强制类型转换,以返回正确的监听器类型,从而满足编译器要求。另一个副作用是由于对
print() 的调用是通过反射间接进行的,所以这个方法必须是公共的(并且不接受参数)。使用
EventHandler
的另一个特点是对于其他版本的
create() 来说,很少出现问题。
使用 create(Class, Object, String, String)
下一版本的
create() 添加了第四个参数,并增加了第三个参数的用途。第一个
String
参数现在也可以表示
Object 参数的可写 JavaBeans 属性的名字。所以,对于
JButton ,如果第三个参数是
text ,那么这相当于一个
setText() 调用,该方法所需要的参数是由传递给第四个参数的
String 来表示的。
第四个参数使您可以访问事件的可读属性,用第三个参数传递的值设置可写属性。为了示范这一点,清单 4 提供了一个用于输入的
JTextField
组件和一个用于文本显示的
JLabel 组件。在
JTextField 中的按 Return
键时,生成一个
ActionEvent ,并且标签的文字变为
JTextField 中的内容。
清单 4. 展示 create(Class, Object, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class TextFieldHandler extends JFrame {
public TextFieldHandler() {
super("Selection");
setDefaultCloseOperation(EXIT_ON_CLOSE);
JTextField text = new JTextField();
JLabel label = new JLabel();
Container contentPane = getContentPane();
contentPane.add(text, BorderLayout.NORTH);
contentPane.add(label, BorderLayout.CENTER);
text.addActionListener(
(ActionListener)EventHandler.create(
ActionListener.class,
label,
"text",
"source.text")
);
}
public static void main(String args[]) {
JFrame frame = new TextFieldHandler();
frame.setSize(200, 150);
frame.show();
}
}
|
图 2 显示了这个程序的外观。在文本字段中输入文字并按 Return 键。这会使
ActionListener
产生
EventHandler.create(ActionListener.class, label, "text", "source.text")
调用,其中
source.text 表明要得到事件源的
text 属性,直接映射到
label.setText((JTextField(event.getSource())).getText())
代码。
图 2. 处理文本字段输入
使用 create(Class, Object, String, String, String)
最后一种版本的
create() 是将另外两种方法结合在一起使用,对于在其他调用中没有的参数,则传递
null 。其他版本的
create() 要求您对所有监听器接口方法做同样的事情,这最后一种方法让您可以指定对每一种监听器方法调用不同的操作。所以,对于一个
MouseListener ,您可以为
mousePressed() 调用一种操作,为
mouseReleased() 调用另一种操作、还可以为
mouseClicked() 调用其他的操作。清单
5 展示了最后一种版本的
create() ,它只有用于鼠标按下/释放事件的两种简单的打印方法:
清单 5. 展示 create(Class, Object, String, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class MouseHandler extends JFrame {
public MouseHandler() {
super("Press and Release Mouse");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Container contentPane = getContentPane();
contentPane.addMouseListener(
(MouseListener)EventHandler.create(
MouseListener.class,
this,
"pressed",
"point",
"mousePressed")
);
contentPane.addMouseListener(
(MouseListener)EventHandler.create(
MouseListener.class,
this,
"released",
"point",
"mouseReleased")
);
}
public void pressed(Point p) {
System.out.println("Pressed at: " + p);
}
public void released(Point p) {
System.out.println("Released at: " + p);
}
public static void main(String args[]) {
JFrame frame = new MouseHandler();
frame.setSize(400, 400);
frame.show();
}
}
|
这个程序没有什么不寻常的地方,只有一个大的空屏幕,可以在其中按下和释放鼠标。不过要注意屏幕附加了两个鼠标监听器,而不是一个。对每个监听器,其他的方法实质上都是沉寂的(stubbed
out)。还要注意
pressed() 和
released() 方法有一个参数是事件的
Point 。对于不接受参数的方法,在指定
point 的地方需要一个
null 。
结束语
这就是有关使用
EventHandler 全部内容。是否要使用它呢?我个人认为这是一种风格问题。在内部它用到了反射,所以可能会稍微慢一些。它还要求调用方法为公共的。如果
IDE 替我生成了代码,那么我可能就让它保持原样,而不会将编码监听器重新编码为匿名内部类。
参考资料
关于作者
对本文的评价
|  |