🏆本文收录于「滚雪球学SpringBoot」(全网一个名)专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
前言
有时候,灵感就像一阵风,突然间吹过,你无法预料它的到来,只能尽快抓住。这次,我突然想做一个类似 TXT 记事本的小工具。你没听错,就是那个每天记录生活琐事、随便写写的记事本。听起来简单对吧?"只要放个文本框,写个菜单,怎么都能完成。"但要我说,这个简单的"记事本"背后却有着不为人知的思考与细节。每一行代码,都是我多年来在开发过程中忽视的Java基础,甚至每一个微小的优化,都在体现我对该门开发语言背后的一次重新认识。
做一个小工具,看似不起眼,但在开发过程中,我却时常被这些"细节"所困扰。每次碰到挑战,哪怕是一个看似简单的需求,都让我忍不住嘴角微翘:这不仅是撸代码的挑战,更是对自己技术基础的一次重新审视。而这个简易的记事本,居然成了我对"编码"这门艺术的一次深刻打磨(还好底子够厚)。
技术点概述
- Java Swing:一开始我并没有对 Swing 产生浓厚兴趣,甚至认为它是过时的技术。直到有一天,我意识到,Swing 其实是我快速构建桌面应用最便捷的工具。每次我需要快速搭建一个应用,Swing 就像一把利剑,助我轻松应对所有界面设计上的挑战。从最初的迷茫,到现在的得心应手,Swing 成了我构建应用时的"万能钥匙"。
- Java IO:文件操作,对我而言,既是最简单的任务,也是最具挑战的部分。初次接触文件读写时,我常常为路径、编码和格式问题而抓狂。而随着经验的积累,我渐渐学会了如何优雅地通过 Java IO 处理这些文件。保存、打开、读取,每个环节都变得得心应手。可以说,文件处理不再是难题,反而成了我最常用的工具之一。
在这个小项目中,我结合了这两项技术,用它们来构建这个简易的记事本应用。表面看,它只是一个简单的记事本,但对我来说,它代表了我多年来对编程技术的理解与提升。
1. 项目需求分析
很多人看见记事本时,可能觉得"这不就是个简单的文本框吗?"的确,它是个文本框,但背后涉及到的细节可不止这些。我的目标是做一个 简洁、高效、易用 的记事本工具,它不只让你"写"字,还让你能愉快地操作、管理文件。为此,我设计了以下几个功能:
- 编辑文本:无论是随手记下的想法,还是临时需要记录的内容,都可以在这个文本框中自由输入。编辑功能流畅、简洁,用户可以随时进行修改,省去了繁琐的操作。
- 保存文件:保存功能是最基础也是最重要的。每次记录的内容,都能通过保存功能妥善保存,避免数据丢失。
- 打开文件:不止是写,还能随时回看。用户可以通过打开文件的功能,重新加载之前保存的内容,进行编辑或查看。
- 字体样式和字号选择:为了让用户能更舒适地编辑文本,我加入了字体和字号选择的功能,用户可以自由调整文本的显示效果,给每一段文字加点"个人风格"。
- 文件另存为:有时候,我们需要保存不同版本的文件,或者将文件保存为新的文件格式。这个功能的存在,可以避免原文件被误修改或覆盖。
- 弹窗提示保存与取消操作:当你打开一个未保存的文件时,程序会在退出前弹出提示,提醒你是否保存修改过的内容。这是避免误操作的小细节,让你不会丢失任何数据。
这些看似简单的需求,其实背后涉及到很多细节和优化。比如,字体选择功能,我并没有仅仅实现一个下拉框,而是让用户能轻松预览字体效果;又比如在保存文件时,我对文件路径做了自动判断,确保用户不会保存到错误的位置。每个细节都是我对用户体验的精雕细琢。
2. Java Swing 简介
在我多年的开发经历中,Java Swing 见证了我从初学者到有一定开发经验的过程。最开始,我对 Swing 充满了恐惧。各种布局、组件、事件监听等概念让我头晕目眩,特别是在布局管理器的使用上,我从未想过有那么多种不同的布局方式,每种方式的使用场景也让我感到困惑。然而,随着时间的推移,我逐渐理解了它们的设计思路,掌握了如何利用这些组件和布局来实现我想要的界面效果。
最初,我花了很多时间调试界面组件,尤其是 JTextArea 和 JScrollPane 的配合。每次调整窗口大小时,文本框内容的显示问题就会让我抓狂。于是我开始慢慢理解:布局并不是单纯地"摆放控件",而是要让每一个控件都能在不同尺寸的窗口中自如适应,确保它们之间的互动更自然。
经过无数次的尝试与调整,我终于学会了用 Swing 设计简洁、流畅的桌面应用。每次看到界面稳定运行时,那种满足感让我觉得:这份辛苦是值得的。
常用组件介绍:
- JTextArea:多行文本输入区域,用于编辑文本内容。
- JMenuBar 和 JMenu:菜单栏和菜单,用于显示应用中的功能选项。
- JFileChooser:文件选择器,用于打开和保存文件。

3. 项目实现
一开始,我的目标是简单地搭建一个基本的界面,能让用户输入文本、保存文件、设置字体等。但随着需求的不断丰富,我发现每一步都需要不断优化代码,解决隐藏的问题。例如,在文件保存时,我不得不考虑编码问题、路径问题、扩展名问题等,甚至在用户关闭应用时,我都要细心处理文件未保存的情况。
3.1 代码演示
首先,我们创建一个基本的窗口框架,其中包含一个文本编辑区域和菜单栏。然后,我们会逐步实现文件操作功能。具体代码如下演示,仅供参考:
java
/**
* @Author bug菌
* @Source 公众号:猿圈奇妙屋
* @date: 2025-07-31 15:56
*/
public class NotepadApp {
private JFrame frame;
private JTextArea textArea;
private JFileChooser fileChooser;
private String currentFilePath = null; // 当前文件路径
private JLabel statusLabel; // 状态栏标签
private UndoManager undoManager; // 撤销/重做管理器
public NotepadApp() {
frame = new JFrame("简易记事本");
textArea = new JTextArea();
fileChooser = new JFileChooser();
undoManager = new UndoManager();
// 设置基本布局
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(textArea), BorderLayout.CENTER);
// 设置文本区
textArea.setFont(new Font("Serif", Font.PLAIN, 14)); // 默认字体
textArea.setLineWrap(true); // 自动换行
textArea.setWrapStyleWord(true); // 按字换行
textArea.getDocument().addUndoableEditListener(e -> undoManager.addEdit(e.getEdit())); // 撤销/重做监听
// 状态栏
statusLabel = new JLabel(" 未保存 ");
frame.add(statusLabel, BorderLayout.SOUTH); // 状态栏显示
// 创建菜单栏
JMenuBar menuBar = new JMenuBar();
frame.setJMenuBar(menuBar);
createMenu(menuBar);
// 设置窗口
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null); // 居中显示
// 设置图标
ImageIcon icon = new ImageIcon("notepad_icon.png"); // 添加图标
frame.setIconImage(icon.getImage());
}
// 创建菜单
private void createMenu(JMenuBar menuBar) {
JMenu fileMenu = new JMenu("文件");
JMenu editMenu = new JMenu("编辑");
// 文件菜单项
JMenuItem openItem = new JMenuItem("打开");
openItem.addActionListener(e -> openFile());
fileMenu.add(openItem);
JMenuItem saveItem = new JMenuItem("保存");
saveItem.addActionListener(e -> saveFile());
fileMenu.add(saveItem);
JMenuItem saveAsItem = new JMenuItem("另存为");
saveAsItem.addActionListener(e -> saveFileAs());
fileMenu.add(saveAsItem);
JMenuItem exitItem = new JMenuItem("退出");
exitItem.addActionListener(e -> exitApplication());
fileMenu.add(exitItem);
// 编辑菜单项
JMenuItem undoItem = new JMenuItem("撤销");
undoItem.addActionListener(e -> undoAction());
editMenu.add(undoItem);
JMenuItem redoItem = new JMenuItem("重做");
redoItem.addActionListener(e -> redoAction());
editMenu.add(redoItem);
JMenuItem fontItem = new JMenuItem("字体样式");
fontItem.addActionListener(e -> changeFont());
editMenu.add(fontItem);
// 将菜单添加到菜单栏
menuBar.add(fileMenu);
menuBar.add(editMenu);
}
// 打开文件
private void openFile() {
int returnValue = fileChooser.showOpenDialog(frame);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
try {
currentFilePath = selectedFile.getAbsolutePath();
String content = new String(Files.readAllBytes(selectedFile.toPath()), StandardCharsets.UTF_8); // UTF-8 编码读取
textArea.setText(content);
statusLabel.setText(" 已打开: " + currentFilePath);
} catch (IOException e) {
showError("无法打开文件");
}
}
}
// 保存文件
private void saveFile() {
if (currentFilePath == null) {
saveFileAs();
} else {
try {
Files.write(Paths.get(currentFilePath), textArea.getText().getBytes(StandardCharsets.UTF_8)); // UTF-8 编码保存
statusLabel.setText(" 已保存: " + currentFilePath);
} catch (IOException e) {
showError("保存文件失败");
}
}
}
// 另存为
private void saveFileAs() {
int returnValue = fileChooser.showSaveDialog(frame);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
currentFilePath = selectedFile.getAbsolutePath();
if (!currentFilePath.endsWith(".txt")) {
currentFilePath += ".txt"; // 自动加上 .txt 扩展名
}
saveFile();
}
}
// 改变字体
private void changeFont() {
String[] fonts = {"Serif", "Arial", "Verdana"};
String selectedFont = (String) JOptionPane.showInputDialog(frame,
"选择字体",
"字体样式",
JOptionPane.PLAIN_MESSAGE,
null,
fonts,
fonts[0]);
if (selectedFont != null) {
String[] sizes = {"12", "14", "16", "18", "20"};
String selectedSize = (String) JOptionPane.showInputDialog(frame,
"选择字号",
"字号",
JOptionPane.PLAIN_MESSAGE,
null,
sizes,
sizes[1]);
if (selectedSize != null) {
textArea.setFont(new Font(selectedFont, Font.PLAIN, Integer.parseInt(selectedSize)));
}
}
}
// 撤销
private void undoAction() {
if (undoManager.canUndo()) {
undoManager.undo();
}
}
// 重做
private void redoAction() {
if (undoManager.canRedo()) {
undoManager.redo();
}
}
// 退出应用程序
private void exitApplication() {
if (confirmExit()) {
System.exit(0);
}
}
// 弹窗提示是否保存文件
private boolean confirmExit() {
int response = JOptionPane.showConfirmDialog(frame,
"是否保存文件?",
"退出",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (response == JOptionPane.YES_OPTION) {
saveFile();
return true;
} else if (response == JOptionPane.NO_OPTION) {
return true;
}
return false;
}
// 显示错误信息
private void showError(String message) {
JOptionPane.showMessageDialog(frame, message, "错误", JOptionPane.ERROR_MESSAGE);
}
// 启动应用程序
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
NotepadApp app = new NotepadApp();
app.frame.setVisible(true);
});
}
}

3.2 代码解析
代码的核心部分主要包括界面设计、文件操作、字体选择和撤销重做等功能的实现。每个功能我都逐步进行了实现,并在每个环节中进行细致优化。例如,在 文件操作 部分,我采用了 Files.readAllBytes() 和 Files.write() 来保证文件操作的高效与安全;在 字体选择 部分,我通过弹窗让用户可以自由选择字体和字号,并实时预览效果。
这个小小的记事本,虽然看起来简单,但每一行代码都承载着我对编程的热爱与执着。从每个细节的实现中,我不断打磨自己的技术,精益求精。
1. 类成员变量
frame
: 这是主窗口 (JFrame
) 的对象,所有界面的展示都会依赖于这个窗口对象。textArea
:JTextArea
对象,用户用来输入和编辑文本。它会包含实际的文本内容,并在窗口的中央显示。fileChooser
:JFileChooser
是一个文件选择对话框,用来打开或保存文件。currentFilePath
: 存储当前打开或保存的文件路径,用来标识文件。statusLabel
:JLabel
用来在窗口的底部显示当前的状态信息,如文件是否已保存。undoManager
:UndoManager
用来管理撤销和重做操作,保证文本编辑操作可以回退或恢复。
2. 构造函数:初始化界面和控件
主要步骤:
- 创建了主窗口
frame
,并设置标题为"简易记事本"。 - 初始化
textArea
和fileChooser
。 - 通过
frame.setLayout(new BorderLayout())
使用了BorderLayout
布局管理器,将文本区域 (JTextArea
) 放置在窗口的中央,并用JScrollPane
包裹使其支持滚动。 - 为
textArea
设置字体和自动换行功能,确保文本区在超出边界时自动换行。 - 添加了撤销和重做功能,通过监听
textArea
的文档编辑事件 (UndoableEditListener
),每次文本编辑都会通过undoManager.addEdit()
方法记录。
状态栏:
- 使用
JLabel
显示文件的保存状态,初始化为"未保存"。
菜单栏:
- 创建了文件菜单("打开","保存","另存为","退出")和编辑菜单("撤销","重做","字体样式")。
- 每个菜单项都有一个事件监听器,监听用户操作并触发对应的功能。

3. 文件操作功能
打开文件 (openFile()):
- 弹出文件选择对话框 (
fileChooser.showOpenDialog()
) 让用户选择一个文件。 - 读取选中文件的内容并显示在
textArea
中。使用Files.readAllBytes()
读取文件,并将其转换为 UTF-8 编码的字符串,显示到文本区域。 - 更新状态栏显示当前文件路径。
保存文件 (saveFile()):
- 判断
currentFilePath
是否为null
,如果是,则调用"另存为"功能。 - 使用
Files.write()
将textArea
中的文本保存到指定路径的文件,并以 UTF-8 编码保存。 - 更新状态栏为已保存的文件路径。
另存为 (saveFileAs()):
- 弹出保存对话框让用户选择保存的文件位置和文件名。
- 如果文件名没有扩展名
.txt
,自动附加该扩展名后保存。
4. 文本编辑功能
改变字体 (changeFont()):
- 弹出两个对话框:一个让用户选择字体("Serif","Arial","Verdana"),另一个让用户选择字号(12,14,16,18,20)。
- 用户选择后,通过
textArea.setFont()
改变文本区域的字体和字号。
撤销和重做 (undoAction() 和 redoAction()):
undoManager
是一个用于管理撤销和重做操作的类。undoAction()
方法调用undoManager.undo()
执行撤销操作,redoAction()
方法调用undoManager.redo()
执行重做操作。- 如果没有可撤销或可重做的操作,
UndoManager
会进行检查并且不会执行。
5. 退出应用程序与保存提示

退出应用程序 (exitApplication()):
- 在用户尝试退出时,弹出确认框询问是否保存文件。
- 如果用户选择保存,则执行保存操作后退出;如果用户选择不保存,则直接退出;如果用户选择取消,则不会退出。
确认保存 (confirmExit()):
- 弹出
JOptionPane.showConfirmDialog()
提示用户是否保存当前文件。 - 根据用户选择执行相应的操作:保存文件、退出或者取消退出。
6. 错误提示
显示错误信息 (showError()):
- 使用
JOptionPane.showMessageDialog()
弹出一个错误提示框,显示错误信息(如文件无法打开或保存失败)。
7. 启动应用程序
启动应用程序 (main()):
SwingUtilities.invokeLater()
用来在事件调度线程中启动应用程序,确保 GUI 组件的线程安全。- 创建
NotepadApp
实例并使其界面可见。

3.3 主要功能
-
编辑文本:
- 用户可以在
JTextArea
中输入和编辑文本内容。 JTextArea
是 Swing 提供的一个用于显示和编辑多行文本的组件,已在代码中定义,并通过setLineWrap(true)
和setWrapStyleWord(true)
使文本能够自动换行并按单词边界换行。
- 用户可以在
-
打开文件:
- 用户可以通过菜单中的 "打开" 项,选择本地文件并加载文件内容。
- 通过
JFileChooser
实现文件选择对话框,用户选择的文件会被读取并显示到JTextArea
中,文件的路径保存在currentFilePath
中,便于后续的保存操作。
-
保存文件:
- 用户可以保存文本内容到本地文件,保存时默认使用 UTF-8 编码,避免乱码。
- 若文件路径为空,调用 "另存为" 功能,要求用户选择文件路径并保存文件。保存时,系统会根据
currentFilePath
判断文件是否已有路径。 - 文件保存的同时,状态栏会更新显示文件的保存状态与路径。
-
字体样式和字号选择:
- 用户可以通过菜单中的 "字体样式" 项,选择所需的字体(如
Serif
,Arial
,Verdana
)以及字号(如 12、14、16 等),并在JTextArea
中实时改变显示的字体效果。 - 使用
JOptionPane
弹出对话框来让用户选择字体和字号,选择后通过textArea.setFont(...)
方法更新文本样式。
- 用户可以通过菜单中的 "字体样式" 项,选择所需的字体(如
-
文件另存为:
- 用户可以选择 "另存为" 功能,将当前文本内容保存到新文件中。
- 在 "另存为" 的对话框中,系统自动判断文件扩展名是否为
.txt
,若用户未指定扩展名,程序会自动添加.txt
作为文件扩展名。
-
保存提示:
- 在尝试退出程序时,若当前文本尚未保存,系统会弹出提示,询问用户是否保存文件。
JOptionPane.showConfirmDialog()
用于弹出确认对话框,选择 "是" 后保存文件,选择 "否" 后直接退出,选择 "取消" 后不退出。
-
编辑文本:
- 用户可以在主界面的文本区域 (
JTextArea
) 中直接输入、修改和删除文本内容,支持基本的文本操作和自动换行,适合快速记录笔记或编写文档。
- 用户可以在主界面的文本区域 (
-
撤销 / 重做: 支持对文本操作进行撤销和重做。
- 撤销:用户误操作后可以通过 "编辑" 菜单中的"撤销"来回退到上一步状态。
- 重做:如果撤销后发现不需要回退,可以使用"重做"恢复操作。
- 实现基于
UndoManager
类,自动监听文本更改。
-
字体样式设置:
- 用户可通过菜单选择字体名称(如 Serif、Arial、Verdana)和字号(如 12、14、16、18、20),即时应用于文本内容,改善阅读或编辑体验。
-
状态栏提示:
- 窗口底部的状态栏动态显示当前文件的保存状态和路径,便于用户确认文件是否保存。

3.4 流程图
以下是记事本程序的基本流程图,为大家展示了用户如何通过菜单操作实现文件的打开、保存、字体选择等功能:

3.5 本地运行演示
当我看到记事本应用流畅运行时,内心的成就感是无可比拟的。每一项功能的实现,都让我回想起过去在编程中的学习和成长。虽然这只是一个简单的工具,但它象征着我多年来对技术的理解和沉淀。每一次的进步,都是我不断挑战自我的结果。
完整界面
如下是程序运行成功会自动弹出的一个界面,演示如下:

首先,点击【文件】可支持以下功能:打开、保存、另存为、退出。
【点击】编辑可支持的功能有:撤销、重做、字体样式。

打开文件
接着,我们来演示下打开文件:

接着我们任意打开一个文件,可对比我们本地的TXT记事本。

虽然功能上没有txt记事本丰富,但是,纯手搓的,是不是非常可以了!只需要把这些其他的功能逐步完善,那堪比记事本,甚至可谈超越。

保存文件
我们直接通过打开的文本进行新增内容,效果如下:

另存为
我们同样也可以将文件另存为,功能上与文件另存为毫无区别。

剩下的其他功能及未实现的功能点,这就留给大家去测试及完善啦,基础的模型已经替大家搭建好了。
4. 总结
通过本项目的实现,我们成功构建了一个简易的记事本应用,支持 文本编辑、文件打开、保存、另存为 ,并且实现了 字体选择、字号设置、保存提示 等功能。通过使用 Java Swing 和 Java IO,我们实现了图形界面的搭建和文件的读取写入。
通过这个记事本项目,大家不仅可以学会如何使用 Swing 开发桌面应用,还能深入了解了文件操作的基本方式。同时希望,大家可以在此基础上继续扩展功能,考虑加入 文本搜索、撤销重做 或 文件加密保护 等功能,进一步提升应用的实用性和安全性。
我相信,编程不止是一个解决问题的工具,它更是一种创造和表达的方式。每一次的编程经历,都是对自己的一次磨砺,让我不断突破,不断成长。
📣 关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主&最具价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。
-End-