简易记事本必须用SwingUtilities.invokeLater启动EDT,JTextArea需启用换行,关闭前用DocumentListener检测修改并提示保存,读写文件须显式指定UTF-8编码,且区分currentFile与lastSavedTo管理路径。

用 JFrame + JTextArea 搭出基础界面,别碰 SwingUtilities.invokeLater 就跑不起来
Java 简易记事本的核心不是功能多全,而是能立刻打开、输入、保存、再打开——界面必须在事件分发线程(EDT)里构建。漏掉 SwingUtilities.invokeLater,窗口可能黑屏、无响应,甚至 JTextArea 不接收键盘输入。
- 所有 Swing 组件(
JFrame、JTextArea、JMenuBar)必须包在SwingUtilities.invokeLater里初始化 -
JTextArea要设setLineWrap(true)和setWrapStyleWord(true),否则长行直接撑破窗口 - 别用
JFrame.pack()后直接setVisible(true)就完事——先调setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE),否则点关闭就彻底退出,来不及弹保存提示
文件读写用 Files.readAllLines 和 Files.write,别手写 BufferedReader / FileWriter
Java 7+ 的 Files 工具类封装了编码、异常、资源释放细节,比手动开流安全得多。尤其 Windows 记事本默认存为 GBK,而 Java 默认用 UTF-8 读会乱码——必须显式指定字符集。
- 读文件:
Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8),拼成字符串用String.join("\n", lines) - 写文件:
Files.write(Paths.get(filePath), content.getBytes(StandardCharsets.UTF_8)),别用getOutputStream()再套Writer,容易漏 flush - 如果用户用系统记事本保存过文件,且含中文,大概率是
GBK编码——可加试探逻辑:先按 UTF-8 读,捕获MalformedInputException后改用GBK
监听关闭和菜单动作时,用 DocumentListener 判断是否修改,而不是靠布尔标记
靠 boolean isModified 手动开关,极易不同步:粘贴、撤销、拖入文本都会漏触发。真正可靠的修改状态,得监听 JTextArea.getDocument() 的底层变更。
- 给
textArea.getDocument()添加DocumentListener,在insertUpdate、removeUpdate、changedUpdate里统一设isModified = true - 保存后立刻调
document.removeDocumentListener(...),再document.addDocumentListener(...),避免重复监听 - 关闭前检查:
if (isModified) { showSaveDialog(); },但注意:用户点“不保存”后,要重置isModified = false,否则下次关窗又弹
“另存为”必须重置当前路径和文件名,否则“保存”会覆盖错误位置
很多初学者把“保存”和“另存为”共用一个 currentFile 字段,但“另存为”选新路径后没更新它,导致后续点“保存”仍写回旧文件——这会让用户以为内容丢了。
立即学习“Java免费学习笔记(深入)”;
- 声明两个字段:
private File currentFile;(当前编辑的源文件)和private File lastSavedTo;(上次实际写入位置),区分语义 - “另存为”成功后,必须同时更新
currentFile = selectedFile和lastSavedTo = selectedFile - “保存”逻辑优先写入
lastSavedTo;若为null(即首次保存),则跳转到“另存为”流程 - 菜单项禁用逻辑:
saveMenuItem.setEnabled(lastSavedTo != null || currentFile != null),避免灰掉却无法操作
public class SimpleNotepad {
private JFrame frame;
private JTextArea textArea;
private File currentFile;
private File lastSavedTo;
public SimpleNotepad() {
SwingUtilities.invokeLater(() -> {
frame = new JFrame("简易记事本");
textArea = new JTextArea();
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
frame.add(new JScrollPane(textArea));
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setSize(600, 400);
setupMenu();
setupCloseHandler();
frame.setVisible(true);
});
}
private void setupCloseHandler() {
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (isModified()) {
int result = JOptionPane.showConfirmDialog(frame,
"内容已修改,是否保存?", "确认", JOptionPane.YES_NO_CANCEL_OPTION);
if (result == JOptionPane.YES_OPTION) {
save();
frame.dispose();
} else if (result == JOptionPane.NO_OPTION) {
frame.dispose();
}
} else {
frame.dispose();
}
}
});
}
private boolean isModified() {
// 实际项目中这里应结合 DocumentListener 状态判断
return false; // 占位,真实逻辑见上文 DocumentListener 部分
}
private void save() {
if (lastSavedTo != null) {
writeToFile(lastSavedTo, textArea.getText());
} else {
saveAs();
}
}
private void saveAs() {
JFileChooser chooser = new JFileChooser();
int result = chooser.showSaveDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
if (!file.getName().endsWith(".txt")) {
file = new File(file.getParent(), file.getName() + ".txt");
}
writeToFile(file, textArea.getText());
currentFile = file;
lastSavedTo = file;
}
}
private void writeToFile(File file, String content) {
try {
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
JOptionPane.showMessageDialog(frame, "保存失败:" + e.getMessage());
}
}
public static void main(String[] args) {
new SimpleNotepad();
}
}
文件编码兼容性、修改状态同步、保存路径管理——这三个点卡住绝大多数人的第一版实现。尤其是“另存为”后忘记更新 currentFile,问题会延迟暴露:用户编辑半小时,点“保存”却写进上周的文件里。










