GUI 编程与事件驱动基础 | JavaSE
GUI 编程与事件驱动基础
一、学习目标
完成本章后,应能够:
- 能够解释 GUI 与 CLI 的区别,理解 GUI 程序为什么主要采用事件驱动模型。
- 能够说明 AWT 与 Swing 的关系,认识常用 Swing 容器与组件。
- 能够独立创建
JFrame窗口,并使用JPanel、JLabel、JButton、JTextField、JPasswordField等组件搭建界面。 - 能够使用常见布局管理器组织组件,而不是完全依赖绝对坐标。
- 能够为按钮、键盘和鼠标操作注册事件监听器,理解事件源、事件对象和监听器之间的关系。
- 能够使用匿名内部类和 Lambda 编写事件处理代码。
- 理解 Swing 的事件派发线程(Event Dispatch Thread, EDT),避免在 GUI 线程中执行长时间阻塞任务。
- 能够独立完成一个简单的登录界面,为下一章石头迷阵 GUI 项目做好准备。
二、核心知识
2.1 什么是 GUI
GUI,全称:
Graphical User Interface
即:
图形用户界面。
GUI 程序通过:
- 窗口
- 按钮
- 文本框
- 标签
- 图片
- 菜单
- 表格
等图形元素与用户交互。
例如:
┌──────────────────────────────┐
│ 用户登录 │
│ │
│ 用户名:[____________] │
│ 密 码:[____________] │
│ │
│ [ 登录 ] [ 取消 ] │
└──────────────────────────────┘
这就是典型 GUI。
2.2 GUI 与 CLI
CLI:
Command Line Interface,命令行界面。
前面开发的学生管理系统就是典型 CLI:
==========学生管理系统==========
1. 添加学生
2. 查询学生
3. 删除学生
4. 修改学生
0. 退出
请选择:
用户主要通过:
键盘输入
↓
Scanner
↓
程序执行
与系统交互。
GUI 则变成:
鼠标点击 / 键盘输入
↓
窗口组件
↓
产生事件
↓
Java事件处理代码
二者核心区别:
| 对比 | CLI | GUI | | ---------- | ---------- | ------------ | | 交互形式 | 命令、文本 | 窗口、按钮等 | | 用户门槛 | 相对高 | 相对低 | | 开发复杂度 | 较低 | 较高 | | 界面表现 | 简单 | 丰富 | | 典型应用 | 命令行工具 | 桌面软件 |
2.3 GUI 最重要的思想:事件驱动
控制台程序经常是:
System.out.println("请输入年龄:");
int age = scanner.nextInt();
System.out.println("请输入姓名:");
String name = scanner.next();
程序决定:
接下来发生什么。
执行流程非常明显:
第一句
↓
第二句
↓
第三句
↓
第四句
但是 GUI 不一样。
窗口显示之后:
用户可能点击登录
用户可能点击取消
用户可能输入用户名
用户可能按回车
用户可能移动鼠标
用户可能关闭窗口
程序根本无法事先知道:
用户下一秒会干什么。
因此 GUI 使用:
事件驱动编程(Event-Driven Programming)
基本思想:
程序先创建界面
↓
等待用户操作
↓
某个事件发生
↓
调用对应事件处理代码
例如:
用户点击登录按钮
↓
产生点击事件
↓
执行登录按钮监听器
↓
读取用户名和密码
↓
验证登录
所以 GUI 程序不是:
“我接下来一定执行什么?”
而是:
“如果某件事情发生,我应该做什么?”
这是非常重要的编程思想转变。
2.4 AWT 与 Swing
JavaSE 中经典 GUI 技术主要涉及:
AWT
Swing
AWT
AWT 全称:
Abstract Window Toolkit
中文通常称:
抽象窗口工具包。
主要包:
java.awt
java.awt.event
AWT 提供:
- 图形
- 颜色
- 字体
- 布局
- 事件模型
- 基础 GUI 能力
例如:
Color
Font
FlowLayout
BorderLayout
GridLayout
ActionEvent
Swing
Swing 主要位于:
javax.swing
例如:
JFrame
JPanel
JLabel
JButton
JTextField
JPasswordField
JTable
JOptionPane
可以把二者简单理解为:
Java GUI
│
┌──────┴──────┐
│ │
AWT Swing
│ │
基础设施 更丰富的组件
└──────┬──────┘
│
一起构建GUI程序
Swing 建立在 AWT 的窗口、事件、绘图等基础设施之上。
所以实际代码经常同时看到:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
这完全正常。
2.5 JFrame:窗口
JFrame 可以理解为:
一个顶级窗口。
例如:
JFrame frame = new JFrame("我的第一个窗口");
你可以把它想象成:
┌────────────────────────────┐
│ 我的第一个窗口 × │
├────────────────────────────┤
│ │
│ 内容区域 │
│ │
└────────────────────────────┘
常用方法:
frame.setSize(500, 400);
设置大小。
frame.setLocationRelativeTo(null);
让窗口居中。
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
关闭窗口时结束程序。
frame.setResizable(false);
禁止调整大小。
frame.setVisible(true);
显示窗口。
注意:
setVisible(true)通常放在界面组件准备完成以后。
2.6 JPanel:面板
JPanel 可以理解为:
窗口内部用于组织组件的小容器。
例如:
JFrame
│
├── JPanel 顶部
│ ├── JLabel
│ └── JTextField
│
├── JPanel 中部
│ └── JTable
│
└── JPanel 底部
├── JButton
└── JButton
代码:
JPanel panel = new JPanel();
frame.add(panel);
实际开发中很少把所有组件乱七八糟直接塞进 JFrame。
通常:
JFrame
↓
多个 JPanel
↓
具体组件
这样结构更加清晰。
2.7 JLabel:文本或图片标签
创建:
JLabel label = new JLabel("用户名:");
可以用于:
显示文字
显示图片
展示状态信息
例如:
JLabel statusLabel = new JLabel("请登录");
点击按钮后:
statusLabel.setText("登录成功");
于是 GUI 内容会发生变化。
2.8 JButton:按钮
创建按钮:
JButton button = new JButton("登录");
注意:
创建按钮 ≠ 点击按钮后自动执行登录。
按钮只是:
GUI组件
还需要给它绑定:
事件监听器
才知道点击以后执行什么。
2.9 JTextField:单行文本输入框
JTextField usernameField =
new JTextField(15);
获取输入:
String username =
usernameField.getText();
修改内容:
usernameField.setText("");
2.10 JPasswordField:密码输入框
密码通常使用:
JPasswordField passwordField =
new JPasswordField(15);
读取密码可以使用:
char[] password =
passwordField.getPassword();
再根据实际业务进行处理。
它与普通 JTextField 的主要视觉差异是:
123456
显示时通常会变成:
••••••
2.11 常见 Swing 组件体系
当前阶段重点掌握:
| 组件 | 主要作用 |
| ---------------- | --------------- |
| JFrame | 顶级窗口 |
| JPanel | 面板容器 |
| JLabel | 文本/图片标签 |
| JButton | 按钮 |
| JTextField | 单行文本输入 |
| JPasswordField | 密码输入 |
| JTextArea | 多行文本 |
| JTable | 表格 |
| JScrollPane | 滚动容器 |
| JOptionPane | 消息/确认对话框 |
现阶段没有必要把所有 Swing API 全背下来。
需要先掌握:
容器
组件
布局
事件
这四个核心概念。
2.12 什么是布局管理器
假设窗口中有三个按钮:
[添加] [删除] [修改]
它们应该:
- 放在哪里?
- 多大?
- 谁在左边?
- 谁在右边?
- 窗口大小变化以后怎么调整?
这就是:
布局(Layout)
问题。
Java 提供:
布局管理器(Layout Manager)
自动管理组件的位置与尺寸。
2.13 为什么不建议完全依赖绝对坐标
一种 GUI 写法是:
button.setBounds(100, 50, 100, 40);
也就是直接规定:
x = 100
y = 50
width = 100
height = 40
这种方式叫:
绝对定位。
简单小游戏或者固定尺寸界面中有时会使用。
但是大型普通 GUI 如果全部这么干:
窗口尺寸改变
字体改变
系统缩放改变
文字长度改变
操作系统改变
都可能导致界面错位。
因此普通表单界面更适合:
布局管理器
2.14 FlowLayout
FlowLayout:
流式布局。
组件从左向右排列。
空间不足时继续换行。
类似:
[A] [B] [C] [D]
[E] [F]
示例:
JPanel panel =
new JPanel(new FlowLayout());
panel.add(new JButton("添加"));
panel.add(new JButton("删除"));
panel.add(new JButton("修改"));
适合:
按钮栏
工具栏
简单操作区域
2.15 BorderLayout
BorderLayout:
边界布局。
将容器分为:
NORTH
┌──────────────────────────┐
│ │
├─────┬──────────────┬─────┤
│WEST │ CENTER │EAST │
├─────┴──────────────┴─────┤
│ SOUTH │
└──────────────────────────┘
五个区域:
BorderLayout.NORTH
BorderLayout.SOUTH
BorderLayout.WEST
BorderLayout.EAST
BorderLayout.CENTER
例如:
frame.setLayout(new BorderLayout());
frame.add(topPanel, BorderLayout.NORTH);
frame.add(contentPanel, BorderLayout.CENTER);
frame.add(bottomPanel, BorderLayout.SOUTH);
它非常适合:
一个窗口的大区域划分。
2.16 GridLayout
GridLayout:
网格布局。
例如:
new GridLayout(2, 3);
意味着:
2 行
3 列
效果:
┌─────┬─────┬─────┐
│ A │ B │ C │
├─────┼─────┼─────┤
│ D │ E │ F │
└─────┴─────┴─────┘
非常适合:
数字键盘
计算器按钮
棋盘
规则表单
网格式操作区
后面的石头迷阵本质上也天然具有:
二维网格
特征。
2.17 BoxLayout
BoxLayout 主要按照:
X_AXIS
或:
Y_AXIS
组织组件。
例如纵向:
用户名
[________]
密码
[________]
[ 登录 ]
它适合:
垂直菜单
表单
纵向工具区域
2.18 一个重要设计原则:组合布局
实际 GUI 通常不是:
整个程序只能选择一种布局。
而是:
JFrame:BorderLayout
│
├── NORTH:FlowLayout
│
├── CENTER:GridLayout
│
└── SOUTH:FlowLayout
也就是说:
不同 JPanel 可以拥有不同布局管理器。
这是构建复杂 GUI 最核心的方法之一。
三、使用方法
3.1 创建第一个 Swing 窗口
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class FirstGuiDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame =
new JFrame("我的第一个 GUI");
frame.setSize(500, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE
);
frame.add(
new JLabel(
"Hello Swing!",
JLabel.CENTER
)
);
frame.setVisible(true);
});
}
}
程序运行后会出现:
┌─────────────────────────────┐
│ 我的第一个 GUI × │
├─────────────────────────────┤
│ │
│ Hello Swing! │
│ │
└─────────────────────────────┘
3.2 创建一个按钮
import javax.swing.*;
public class ButtonDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame =
new JFrame("按钮");
JPanel panel =
new JPanel();
JButton button =
new JButton("点我");
panel.add(button);
frame.add(panel);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE
);
frame.setVisible(true);
});
}
}
但是现在点击:
[点我]
什么也不会发生。
因为还没有:
监听器
3.3 事件驱动模型
GUI 事件可以建立如下模型:
事件源
↓
产生事件
↓
事件对象
↓
通知监听器
↓
执行处理方法
例如点击按钮:
JButton
↓
用户点击
↓
ActionEvent
↓
ActionListener
↓
actionPerformed()
四个核心概念:
事件源(Event Source)
谁产生事件。
例如:
JButton loginButton;
这里:
loginButton
就是事件源。
事件(Event)
发生了什么事情。
例如:
按钮被点击
键盘被按下
鼠标被移动
事件对象(Event Object)
Java 会把事件相关信息封装成对象。
例如:
ActionEvent
KeyEvent
MouseEvent
监听器(Listener)
监听某类事件,并规定事件发生以后:
执行什么代码。
例如:
ActionListener
KeyListener
MouseListener
3.4 ActionListener
按钮点击最常见:
button.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(
ActionEvent e) {
System.out.println(
"按钮被点击了"
);
}
}
);
流程:
用户点击 JButton
↓
产生 ActionEvent
↓
通知 ActionListener
↓
调用 actionPerformed()
↓
执行我们的代码
3.5 监听器写法一:单独实现类
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class LoginListener
implements ActionListener {
@Override
public void actionPerformed(
ActionEvent e) {
System.out.println(
"用户点击登录"
);
}
}
注册:
loginButton.addActionListener(
new LoginListener()
);
优点:
事件逻辑可以单独组织
代码职责明确
缺点:
简单事件需要额外创建类
3.6 监听器写法二:匿名内部类
loginButton.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(
ActionEvent e) {
System.out.println(
"登录"
);
}
}
);
前面学习的:
匿名内部类
终于出现了一个非常典型的实际应用。
3.7 监听器写法三:Lambda
ActionListener 是函数式接口,因此可以:
loginButton.addActionListener(
e -> System.out.println("登录")
);
如果事件处理逻辑较多:
loginButton.addActionListener(e -> {
String username =
usernameField.getText();
System.out.println(
"用户名:" + username
);
});
这也是之前学习 Lambda 的真实应用场景。
3.8 完整登录窗口
下面将:
窗口
布局
标签
文本框
密码框
按钮
事件监听
组合起来。
import javax.swing.*;
import java.awt.*;
public class LoginFrame extends JFrame {
private final JTextField usernameField =
new JTextField(15);
private final JPasswordField passwordField =
new JPasswordField(15);
private final JLabel resultLabel =
new JLabel("请输入账号密码");
public LoginFrame() {
setTitle("登录系统");
setSize(420, 250);
setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE
);
setLocationRelativeTo(null);
initView();
setVisible(true);
}
private void initView() {
JPanel formPanel =
new JPanel(
new GridLayout(
3,
2,
10,
10
)
);
formPanel.add(
new JLabel("用户名:")
);
formPanel.add(
usernameField
);
formPanel.add(
new JLabel("密码:")
);
formPanel.add(
passwordField
);
JButton loginButton =
new JButton("登录");
JButton clearButton =
new JButton("清空");
formPanel.add(loginButton);
formPanel.add(clearButton);
JPanel statusPanel =
new JPanel();
statusPanel.add(resultLabel);
setLayout(
new BorderLayout(
10,
10
)
);
add(
formPanel,
BorderLayout.CENTER
);
add(
statusPanel,
BorderLayout.SOUTH
);
loginButton.addActionListener(
e -> login()
);
clearButton.addActionListener(
e -> clearInput()
);
}
private void login() {
String username =
usernameField
.getText()
.trim();
String password =
new String(
passwordField
.getPassword()
);
if (
"admin".equals(username)
&&
"123456".equals(password)
) {
resultLabel.setText(
"登录成功"
);
} else {
resultLabel.setText(
"用户名或密码错误"
);
}
}
private void clearInput() {
usernameField.setText("");
passwordField.setText("");
resultLabel.setText(
"请输入账号密码"
);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
LoginFrame::new
);
}
}
这段代码的调用关系:
main()
↓
创建 LoginFrame
↓
构造器
↓
initView()
↓
创建组件
↓
注册事件
↓
setVisible(true)
↓
等待用户事件
点击登录:
JButton
↓
ActionEvent
↓
Lambda监听器
↓
login()
↓
读取输入框
↓
验证账号
↓
修改 JLabel
3.9 JOptionPane 消息框
除了修改 JLabel,GUI 中经常弹出提示框:
JOptionPane.showMessageDialog(
this,
"登录成功"
);
错误:
JOptionPane.showMessageDialog(
this,
"用户名或密码错误",
"登录失败",
JOptionPane.ERROR_MESSAGE
);
确认框:
int result =
JOptionPane.showConfirmDialog(
this,
"确定退出系统吗?",
"确认",
JOptionPane.YES_NO_OPTION
);
然后:
if (
result
==
JOptionPane.YES_OPTION
) {
System.exit(0);
}
3.10 键盘事件
GUI 不只有:
按钮点击
后面的石头迷阵会大量需要:
键盘事件
例如监听按键:
addKeyListener(
new java.awt.event.KeyAdapter() {
@Override
public void keyPressed(
java.awt.event.KeyEvent e) {
System.out.println(
"按键:" + e.getKeyCode()
);
}
}
);
通过:
e.getKeyCode()
可以判断:
KeyEvent.VK_UP
KeyEvent.VK_DOWN
KeyEvent.VK_LEFT
KeyEvent.VK_RIGHT
于是:
↑
↓
←
→
就能够驱动程序逻辑。
这正是下一章石头迷阵的重要基础。
3.11 鼠标事件
鼠标相关事件可以使用:
MouseListener
或者更加方便的:
MouseAdapter
例如:
panel.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(
MouseEvent e) {
System.out.println(
"鼠标点击"
);
}
}
);
常见事件:
mouseClicked
mousePressed
mouseReleased
mouseEntered
mouseExited
四、原理与进阶
4.1 为什么 GUI 程序不会在 main 结束后立即退出
你可能会产生疑问:
public static void main(String[] args) {
new LoginFrame();
}
看起来:
new LoginFrame()
执行结束以后,main() 好像也结束了。
为什么窗口还活着?
因为 Swing GUI 建立以后,程序中还存在 GUI 事件处理相关线程。
程序进入类似:
等待事件
↓
处理事件
↓
继续等待
的状态。
GUI 的运行方式已经不同于简单的:
main()
↓
逐行执行
↓
结束
4.2 事件派发线程 EDT
Swing 中有一个非常重要的线程:
事件派发线程(Event Dispatch Thread, EDT)
它主要负责:
处理GUI事件
更新Swing组件
执行事件监听代码
例如:
loginButton.addActionListener(e -> {
resultLabel.setText("登录成功");
});
用户点击按钮以后,这段监听代码通常就在:
EDT
中执行。
4.3 为什么使用 SwingUtilities.invokeLater
推荐这样启动 GUI:
SwingUtilities.invokeLater(
LoginFrame::new
);
理解为:
main线程
↓
把“创建GUI”任务
提交给
↓
EDT
↓
EDT创建GUI
而不是:
public static void main(String[] args) {
new LoginFrame();
}
直接让 main 线程创建界面。
在简单演示中后者经常看起来也能工作,但是建立正确 Swing 编程习惯时,应认识 EDT。
4.4 为什么不能在按钮事件中执行长任务
例如:
button.addActionListener(e -> {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
});
如果监听器运行在 EDT:
EDT
↓
sleep 10秒
那么这十秒内:
鼠标事件不能及时处理
按钮不能及时响应
窗口不能及时重绘
最终表现:
界面卡死。
所以必须形成一个重要认识:
短小GUI操作
→ EDT
耗时任务
→ 后台线程
结果更新GUI
→ EDT
这与前面学过的:
多线程
开始真正联系起来。
4.5 事件驱动与普通方法调用的关系
事件驱动并没有创造一种新的 Java 方法。
本质依旧是:
调用方法
只不过以前:
login();
是:
程序员主动调用。
现在:
button.addActionListener(
e -> login()
);
变成:
用户触发事件以后,由事件系统间接调用。
所以:
面向对象
接口
匿名内部类
Lambda
方法调用
多线程
并没有消失。
它们只是被组合到 GUI 体系中了。
4.6 监听器为什么经常和 Lambda 联系起来
例如:
public interface ActionListener
extends EventListener {
void actionPerformed(ActionEvent e);
}
它只有一个抽象方法:
actionPerformed(...)
因此:
ActionListener
满足:
函数式接口。
所以:
new ActionListener() {
@Override
public void actionPerformed(
ActionEvent e) {
login();
}
}
可以简化成:
e -> login()
进一步:
this::login
只有在参数形式能够匹配对应方法时才能使用方法引用,因此不要为了“炫技”机械改写。
五、实践应用
5.1 登录系统
登录界面非常适合练习:
JFrame
JPanel
JLabel
JTextField
JPasswordField
JButton
GridLayout
BorderLayout
ActionListener
流程:
用户名输入
+
密码输入
↓
点击登录
↓
读取表单
↓
数据校验
↓
登录成功 / 失败
5.2 人事管理系统
原教学案例进一步使用:
JTable
DefaultTableModel
JTextField
JButton
JPopupMenu
MouseListener
ArrayList<Employee>
可以形成:
┌────────────────────────────────────┐
│ [姓名________] [搜索] [新增] │
├────────────────────────────────────┤
│ ID │ 姓名 │ 性别 │ 年龄 │ 部门... │
│----│------│------│------│---------│
│ 1 │ 张三 │ 男 │ 24 │ 开发部 │
│ 2 │ 李四 │ 女 │ 25 │ 测试部 │
└────────────────────────────────────┘
此时:
GUI
+
ArrayList
+
OOP
+
事件驱动
已经组合起来。
5.3 石头迷阵
下一章会进一步组合:
JFrame
+
二维数组
+
图片
+
键盘事件
+
枚举
+
随机算法
+
文件
+
游戏状态
其中 GUI 的职责主要是:
把程序状态显示出来
+
接收用户操作
而真正的游戏状态:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 0 15
仍然应该由:
int[][]
等数据结构维护。
这里可以提前形成一个非常重要的设计意识:
界面只是数据和业务状态的表现形式,不要让 GUI 完全替代业务数据模型。
5.4 即时通信系统
后面的聊天室则会组合:
Swing GUI
+
Socket
+
IO
+
线程
用户点击:
[发送]
产生事件:
ActionListener
然后:
获取 JTextArea 内容
↓
Socket
↓
DataOutputStream
↓
服务器
这就是 JavaSE 各模块真正开始汇合的地方。
六、常见问题
6.1 创建了 JFrame 为什么看不到
最常见原因:
没有:
frame.setVisible(true);
创建对象只表示:
窗口对象存在
不代表:
窗口已经显示。
6.2 点击关闭按钮为什么程序没有退出
需要:
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE
);
否则窗口关闭行为可能与你期待的“结束整个程序”不同。
6.3 JButton 为什么点了没有反应
因为:
JButton button =
new JButton("登录");
只创建组件。
还必须注册:
button.addActionListener(...);
6.4 为什么所有组件挤在一起
很可能:
布局管理器使用不合理。
GUI 不是:
add()
add()
add()
add()
无限堆组件。
需要先设计:
窗口区域
↓
面板
↓
布局
↓
组件
6.5 JFrame 和 JPanel 有什么区别
可以粗略理解:
JFrame = 房子
JPanel = 房间
JButton/JLabel/JTextField
= 房间里的家具
通常:
JFrame
↓
JPanel
↓
组件
6.6 为什么事件监听器里可以直接访问输入框
例如:
loginButton.addActionListener(e -> {
String username =
usernameField.getText();
});
Lambda 可以捕获所在作用域中满足规则的变量。
如果:
usernameField
本身还是对象字段:
private JTextField usernameField;
事件处理方法自然可以直接访问。
6.7 为什么 GUI 卡住了
优先检查:
是否在事件处理方法中执行了耗时操作。
例如:
while (true) {
}
或者:
Thread.sleep(10000);
或者:
大型文件操作
耗时网络请求
复杂计算
如果这些直接占用 EDT:
GUI无法及时处理事件
于是表现为:
卡死
未响应
6.8 是不是必须背所有 Swing 组件
不是。
这一章真正需要建立的是:
GUI
│
├── 容器
│
├── 组件
│
├── 布局
│
└── 事件
再进一步:
事件源
↓
事件对象
↓
监听器
↓
业务方法
有了这套模型,再查 API 就会非常容易。
6.9 Swing 现在是不是 Java 后端开发的核心技术
不是。
如果职业方向是:
Java Web
Spring
Spring Boot
微服务
那么 Swing 不会成为后端学习主线。
但是当前学习 Swing 的价值非常明确:
GUI编程
事件驱动
监听器模式
Lambda应用
组件化思维
多线程与UI线程
项目综合训练
特别是后面的:
石头迷阵
局域网聊天室
都需要 GUI。
因此本课程学习目标不是:
成为 Swing 专家。
而是:
通过 GUI 完成 JavaSE 综合能力闭环。
七、练习与验收
7.1 知识问答
- GUI 与 CLI 分别是什么?适用场景有什么不同?
- AWT 与 Swing 是什么关系?
JFrame、JPanel、JLabel、JButton、JTextField、JPasswordField分别承担什么职责?- 什么是布局管理器?
- 为什么普通 GUI 不建议所有组件完全依赖绝对坐标?
FlowLayout、BorderLayout、GridLayout、BoxLayout分别适合什么场景?- 什么是事件驱动编程?
- 什么是事件源?
- 什么是事件对象?
- 什么是事件监听器?
- 用户点击
JButton后,从用户操作到 Java 方法执行,大致经历什么过程? ActionListener为什么能够使用 Lambda?- 什么是事件派发线程 EDT?
- 为什么 Swing 事件处理方法中不适合执行长时间任务?
- 为什么 GUI 程序可以看成“等待事件 → 处理事件 → 继续等待”?
7.2 代码阅读
阅读:
JButton button =
new JButton("确定");
JLabel label =
new JLabel("等待操作");
button.addActionListener(e -> {
label.setText("已经点击");
});
禁止运行程序,回答:
- 事件源是谁?
- 监听器是什么?
- 用户点击按钮以后发生什么?
e大致代表什么?- 哪一行真正修改了界面状态?
- 如果删除
addActionListener,按钮还能显示吗? - 删除后点击按钮还会修改标签吗?
阅读:
JFrame frame =
new JFrame();
frame.setLayout(
new BorderLayout()
);
frame.add(
new JButton("A"),
BorderLayout.NORTH
);
frame.add(
new JButton("B"),
BorderLayout.CENTER
);
frame.add(
new JButton("C"),
BorderLayout.SOUTH
);
回答:
- 三个按钮分别位于什么区域?
- 哪一个通常占据最大空间?
- 如果再次向
CENTER添加另一个普通组件,应该重点观察什么现象? - 如果想在 SOUTH 同时排列三个按钮,应该如何增加
JPanel进行组合布局?
7.3 手写代码
禁止复制本章示例。
任务一:第一个窗口
从零创建:
标题:星雨笔录
宽:500
高:400
居中显示
关闭窗口退出程序
任务二:登录窗口
要求包含:
标题
用户名
密码
登录按钮
取消按钮
状态标签
要求至少使用:
JFrame
JPanel
JLabel
JTextField
JPasswordField
JButton
任务三:布局实验
分别使用:
FlowLayout
BorderLayout
GridLayout
BoxLayout
制作四个小窗口。
记录:
组件排列规律
窗口缩放表现
适合场景
任务四:事件监听
创建:
[增加]
按钮。
每点击一次:
计数:0
↓
计数:1
↓
计数:2
↓
计数:3
要求界面直接显示当前次数。
任务五:匿名内部类改 Lambda
先写:
new ActionListener() {
@Override
public void actionPerformed(
ActionEvent e) {
// ...
}
}
确认理解以后,再改成:
e -> {
// ...
}
不得直接跳过第一版。
7.4 Debug
下面代码为什么无法显示正常窗口?
public class GuiDebug01 {
public static void main(String[] args) {
JFrame frame =
new JFrame("Demo");
frame.setSize(400, 300);
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE
);
}
}
要求:
- 找出问题。
- 说明问题属于编译错误还是逻辑问题。
- 修复代码。
分析:
button.addActionListener(e -> {
while (true) {
System.out.println("running");
}
});
回答:
- 点击按钮后 GUI 可能发生什么?
- 为什么?
- 与 EDT 有什么关系?
- 应该如何重新设计长时间任务?
分析:
JPanel panel =
new JPanel(
new GridLayout(2, 2)
);
panel.add(new JButton("1"));
panel.add(new JButton("2"));
panel.add(new JButton("3"));
panel.add(new JButton("4"));
panel.add(new JButton("5"));
在运行前先预测:
布局结果是否一定还是你想象中的严格 2×2?
实际运行后记录观察结果,并查阅对应 API 行为解释原因。
7.5 综合训练
实现一个:
简单人事管理 GUI
暂时不要求数据库。
使用:
ArrayList<Employee>
保存员工。
Employee 至少包含:
id
name
gender
age
department
GUI 至少具有:
员工列表
姓名搜索
新增员工
删除员工
建议:
JFrame
│
├── NORTH
│ ├── JTextField
│ ├── 搜索按钮
│ └── 新增按钮
│
├── CENTER
│ └── JTable
│
└── SOUTH
└── 状态信息
项目重点不是界面美化,而是:
集合数据
↓
GUI展示
↓
用户事件
↓
修改集合
↓
刷新GUI
理解数据与界面之间的关系。
7.6 本章验收
本章达到以下标准,可以视为掌握。
概念能力
不查看资料能够解释:
GUI
CLI
AWT
Swing
JFrame
JPanel
布局管理器
事件驱动
事件源
事件对象
监听器
EDT
手写能力
能够独立写出:
JFrame
JPanel
JButton
JTextField
JLabel
并完成:
创建
添加
布局
显示
监听
事件能力
看到:
button.addActionListener(
e -> doSomething()
);
能够立刻解释:
button
→ 事件源
点击
→ ActionEvent
ActionListener
→ 监听器
doSomething()
→ 实际业务逻辑
布局能力
能够根据需求选择:
FlowLayout
BorderLayout
GridLayout
BoxLayout
而不是所有界面:
setLayout(null);
然后疯狂:
setBounds(...)
综合能力
关闭本教程和 AI 自动补全,从空项目开始完成:
一个具有用户名、密码、登录、取消按钮和状态提示的 Swing 登录窗口。
并能将:
匿名内部类监听器
改写成:
Lambda监听器
最后能够完整口述:
用户点击按钮
↓
组件产生事件
↓
事件系统通知监听器
↓
监听器执行回调
↓
业务代码改变状态
↓
GUI更新
如果这条链路能够真正解释清楚,就已经具备进入石头迷阵 GUI 综合项目的基础。