1. 写在前面:为什么 2025 年还在聊 Swing
先说个现状,很多 Java 开发者一听到 “Swing” 就皱眉,觉得这东西老、丑、过时。但实际上,Swing 依然是 JVM 生态里最成熟、最稳定、资料最全的桌面 GUI 方案之一。不少企业内部的运维工具、数据看板、客户端管理系统,跑在生产环境里的界面就是 Swing 写的。你去看一些银行、制造业、物流行业的机房,那些屏幕上的老系统,翻开源码十有八九是 Swing。
所以这篇文章不是带着大家重新翻一遍 JButton、JLabel 的 API 文档,而是从一个实战角度,把 Swing 真正用“现代化”的思路重构一遍。我会结合自己做过的一个桌面客户端项目,从界面布局、主题美化、异步任务、打包分发这几个维度,把踩过的坑、验证过好用的方案、以及背后“为什么要这么做”的逻辑全部摊开讲。
适合看这篇文章的人,主要是三类:一是刚学完 Java 基础、想找个真实项目练手的同学;二是工作中突然接到 Swing 维护或改造任务、急需系统补课的开发者;三是想评估“Swing 到底能不能做出现代化界面”的技术决策者。
需要提前说明的是,这篇文章不涉及任何第三方付费组件,全程基于原生 JDK 自带的 Swing 库,配合少量开源辅助库。只要你本机装了 JDK 8 或更高版本,就能跟着一步步跑起来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计与方案选型:先把“现代化”这件事定义清楚
2.1 “现代化”到底指什么
很多人对 Swing 的刻板印象是灰色按钮、僵硬布局、丑到没法看。但说实话,这锅不该完全让 Swing 背。Java 早期界面丑,主要是因为开发者普遍不重视 UI 细节,而且当时硬件性能和系统渲染能力也有限。现在你用的电脑,屏幕分辨率、显卡渲染能力、系统级字体渲染都已经远超 Swing 诞生时的环境,只要设计得当,Swing 界面完全可以做到和 JavaFX、Electron 不相上下的视觉体验。
我用一个词来概括现代化桌面应用的核心诉求:克制。界面干净、配色统一、间距合理、交互反馈即时。不需要花哨的动画和毛玻璃效果,但基础的 hover 反馈、焦点状态、布局随窗口缩放的自适应,这些必须有。
所以我在设计这个实战项目时,给自己定了几个硬性指标:
- 布局:使用 BorderLayout + GridBagLayout 的组合,不用绝对定位。
- 配色:定义一套主题色板,所有组件统一从主题中取色,不散落硬编码颜色。
- 字体:使用全局字体设置,统一调整字号和字重。
- 操作反馈:按钮点击、列表选中、数据加载等场景,必须有明确的视觉反馈或状态提示。
- 响应式:窗口拉伸时,内容区能自适应缩放,而不是组件挤在一起或大片留白。
2.2 为什么选择 Swing 而不是 JavaFX
这个话题几乎每次都会被问到。JavaFX 确实是 Oracle 主推的新一代 UI 框架,支持 CSS 样式、硬件加速、更现代的渲染管线。但我个人的选择逻辑是看场景。
如果你的项目是从零开始、没有任何历史包袱、且团队愿意学习新框架,那 JavaFX 确实值得投入。但如果你的目标是快速交付一个稳定可靠的工具类桌面应用,或者你正在维护一个既有 Swing 项目,Swing 的低门槛和高稳定性就是巨大优势。
Swing 的核心优势有三个:
- JDK 自带,零额外依赖。任何装好 JDK 的机器都能直接跑,不需要额外安装运行时。
- 组件体系成熟稳定。二十多年的迭代,各种边界情况基本都被踩平了,网上能搜到几乎所有问题的答案。
- 内存占用和启动速度可控。相比 Electron 动辄两三百 MB 的内存,Swing 应用一般也就几十 MB,轻量得多。
当然 Swing 也有它明显的短板:默认外观老旧、不支持 CSS、复杂动画实现成本高。但这些都有对应的解决方案,后面我会逐个细讲。
2.3 项目背景与功能范围
为了让这篇博文不那么“空对空”,我给自己设定了一个具体的实战场景:做一个员工信息管理桌面客户端。功能包括部门树浏览、员工列表展示、员工新增/编辑对话框、数据检索、Excel 导入导出。这个场景很典型,既能覆盖 Swing 大部分常用组件(JTree、JTable、JDialog、JFormattedTextField),又能延伸到异步加载、线程安全更新 UI 等真实项目避不开的难点。
这个项目我建议你打开 IDE 跟着敲,不要只看不练。代码不长,核心部分大概在 1500 行左右,但每一块的取舍都有讲究。
3. 环境准备与工程骨架搭建
3.1 JDK 版本选择与安装细节
做 Swing 开发,我强烈建议直接用 JDK 17 或更高版本。原因不仅仅是 Java 17 是 LTS 版本,更关键的是从 JDK 9 开始,Swing 转入模块化体系(java.desktop 模块),很多老教程里那套“默认导入全部”的写法已经不适用了。而 JDK 17 在性能、GC 稳定性、容器支持上都有明显进步,桌面应用跑起来体感更流畅。
安装时有一个细节容易踩坑:如果你装了多个 JDK 版本,一定要确认 JAVA_HOME 指向正确。因为后面打包阶段我会用到 jpackage 工具,它会直接读取 JAVA_HOME 环境变量,指错了会让你白折腾半小时。
验证安装是否成功,在终端里执行:
bash复制java -version
javac -version
看到版本号输出了,就说明没问题。顺便说一句,如果你的系统里只有 JRE 没有 JDK,那是没法编译的,Swing 开发必须装完整 JDK。
3.2 构建工具选择:Maven 还是 Gradle
国内 Java 生态里 Maven 依然是绝对主流,所以我默认使用 Maven 来管理依赖和构建。不管你是 IntelliJ IDEA 还是 Eclipse,新建一个 Maven 项目都很简单。关键是在 pom.xml 里设置正确的编译级别,避免默认的 Java 5 级别导致语法报错:
xml复制<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
这里有个细节值得注意:project.build.sourceEncoding 必须显式设置为 UTF-8。Swing 界面里如果包含中文,而编译编码不是 UTF-8,轻则界面上出现乱码,重则直接编译失败。我见过太多人在这里栽跟头了,界面上的中文要么是问号,要么是方块,原因基本都在编码配置上。
3.3 包结构与主类设计
工程建好后,我采用的包结构如下:
code复制com.example.employee
├── App.java
├── ui
│ ├── MainFrame.java
│ ├── EmployeeTableModel.java
│ ├── EmployeeDialog.java
│ └── DepartmentTree.java
├── model
│ ├── Employee.java
│ └── Department.java
├── service
│ ├── EmployeeService.java
│ └── DataImporter.java
└── theme
├── AppTheme.java
└── UiUtils.java
这个分层思路很直观:model 放数据实体,service 放业务逻辑,ui 放界面组件,theme 统一管理样式和工具方法。为什么这么分?因为 Swing 项目最大的痛点就是逻辑和界面耦合,一个窗体类里既查数据库又渲染表格,写到后面基本没法维护。保持单向依赖关系——界面层可以调用业务层,但业务层绝不允许反向依赖界面层——能让你在后续扩展功能时省下大量时间。
主类 App.java 是整个程序的入口。记住一个关键点:所有 Swing 操作必须在事件调度线程(EDT)中执行。入口写法如下:
java复制public class App {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(new FlatLightLaf());
} catch (Exception ex) {
ex.printStackTrace();
}
new MainFrame().setVisible(true);
});
}
}
SwingUtilities.invokeLater 的作用是把初始化 UI 的任务丢到 EDT 队列里,确保界
