1. Swing 和 AWT 的超简入门
如果把 Java 比作汽车,那么 Swing 和 AWT 就是它的“仪表盘”和“方向盘”。AWT (Abstract Window Toolkit) 是 Java 中最早用于创建桌面窗口应用的库。它使用操作系统的原生控件,因此在 Windows、Linux 和 Mac 上的外观各不相同。
Swing 是在 AWT 之上的更现代的封装,完全由 Java 编写。Swing 更美观、更灵活,支持大量额外功能,并且在各平台上看起来基本一致(当然,也不完全一样)。在这两者中,所有界面元素(按钮、文本框、复选框等)都支持事件处理。
在本讲中我们将使用 Swing:对新手更友好,也更常见于教学示例。接下来以最常见的任务——响应按钮点击——为例,来看看事件处理是如何运作的。
创建按钮
在 Swing 中,按钮这样创建:
JButton button = new JButton("点我!");
这里的“点我!”是按钮上的文字。
添加监听器
要对点击做出响应,需要为事件“注册”监听器:
button.addActionListener(new MyActionListener());
那么这个 MyActionListener 是谁?它是一个实现了接口 ActionListener 的类:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮已被点击!");
}
}
当用户点击按钮时,Swing 会调用所有通过 addActionListener 注册的监听器的 actionPerformed 方法。
完整示例:带按钮的简单窗口
把这些放在一起,创建一个可运行的应用:
import javax.swing.*;
import java.awt.event.*;
public class SimpleButtonApp {
public static void main(String[] args) {
JFrame frame = new JFrame("事件示例");
JButton button = new JButton("点我!");
// 给按钮添加监听器
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮已被点击!");
JOptionPane.showMessageDialog(frame, "太棒了!按钮被按下了!");
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
这里发生了什么:
- 创建窗口(JFrame)。
- 创建按钮(JButton)。
- 添加监听器(使用匿名类,避免单独建文件)。
- 在 actionPerformed 中向控制台和弹窗(JOptionPane)输出消息。
- 把按钮加到窗口并显示给用户。
自己动手试试 复制这段代码、运行——然后点击按钮!
2. 匿名类与 Lambda 表达式
在现代 Java 代码里,为一个处理器单独写一个类,就像开坦克去买面包。更方便的是使用 匿名类 或 Lambda 表达式。
匿名类
我们在上面已经见过这种写法:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 处理代码
}
});
这里的类就地声明,没有名字。
Lambda 表达式(Java 8+)
如果监听器接口是函数式的(也就是只包含一个抽象方法),就可以使用 Lambda:
button.addActionListener(e -> {
System.out.println("通过 Lambda 点击了按钮!");
});
如果只有一行代码,还可以更简洁:
button.addActionListener(e -> System.out.println("Lambda:按钮被点击!"));
这不仅能让代码更短,也更易读。冷知识:接口 ActionListener 是函数式接口,因为它只有一个方法 actionPerformed。
4. 其他事件类型
世界不止有按钮!在 Swing 和 AWT 中,几乎每个界面元素都有自己的事件。常见的有:
鼠标事件:MouseListener 和 MouseAdapter
如果需要响应鼠标点击,可以使用 MouseListener:
button.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("鼠标点击了按钮!");
}
// 其余方法可以留空
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
为了一个处理器写五个空方法并不方便。此时可以用 MouseAdapter:
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("鼠标点击了按钮(通过适配器)!");
}
});
MouseAdapter 已经实现了接口中的所有方法,你只需重写需要的那些。
键盘事件:KeyListener/KeyAdapter
如果需要捕获按键:
button.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("按下的键:" + e.getKeyChar());
}
});
文本变化事件:DocumentListener
对于文本输入组件(JTextField、JTextArea)有专门的监听器:
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
System.out.println("文本已改变:" + textField.getText());
}
@Override public void removeUpdate(DocumentEvent e) {}
@Override public void changedUpdate(DocumentEvent e) {}
});
顺便说一句,多数事件都有相应的适配器类,可以避免书写空方法.
5. 实战:带窗口和按钮的迷你示例
我们来做一个最简单的 GUI 应用:一个带按钮的窗口,点击按钮会递增计数并显示消息。
示例:点击计数器
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ClickCounterApp {
public static void main(String[] args) {
JFrame frame = new JFrame("点击计数器");
JButton button = new JButton("点我!");
JLabel label = new JLabel("点击次数:0");
// 点击计数器(必须是 final 或 effectively final)
final int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText("点击次数:" + count[0]);
});
frame.setLayout(new FlowLayout());
frame.add(button);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250, 100);
frame.setVisible(true);
}
}
说明:
- 创建窗口、按钮和用于显示计数的标签。
- 使用一个单元素数组(final int[] count = {0};),以绕过 Lambda 对 final/effectively final 变量的限制。
- 每次点击按钮时递增计数并更新标签文本。
练习点子:试着在每第5次点击时改变按钮颜色!
6. 事件模型在内部如何运作
让我们稍微看看底层。当你调用:
button.addActionListener(listener);
按钮(JButton 对象)会把你的监听器加入内部列表。 当用户点击按钮时,内部会发生以下步骤:
- 按钮创建事件对象(ActionEvent)。
- 按钮遍历监听器列表并调用每个监听器的 actionPerformed 方法。
- 你的处理器执行相应动作(例如更新标签)。
要点:每个事件都可以有任意数量的监听器——你可以为同一按钮添加多个处理器,它们会依次被调用。
7. 可视化示意:事件处理流程
graph TD
A[用户点击按钮] --> B{JButton}
B --> C[创建 ActionEvent]
C --> D[调用所有监听器的 actionPerformed]
D --> E[处理器执行操作]
7. 在 Swing 和 AWT 处理中常见的错误
错误 1:忘记注册监听器。 按钮不响应点击,因为忘记调用 addActionListener。
错误 2:在处理器里做重活。 如果在事件处理器中执行耗时循环或下载网络资源,界面会“卡住”。对于耗时任务请使用单独的线程或 SwingWorker。
错误 3:在 Lambda 中修改非 final/effectively final 的变量。 在 Lambda 中只能使用 final 或“effectively final”的变量。对于计数器可以使用数组或专用类(想更专业些可用 AtomicInteger)。
错误 4:忘记移除监听器。 如果删除组件但没有移除其监听器,可能造成内存泄漏。当组件不再需要时,请取消订阅。
错误 5:接口的方法未全部实现。 如果直接实现例如 MouseListener,别忘了实现所有方法!更好的做法是使用适配器(MouseAdapter、KeyAdapter)。
GO TO FULL VERSION