尧图精选

Java基础练习选择题 3 已整理:用Swing做线程与JDBC错题自测面板

🕒 发布时间:2026/10/1 22:19:58 📁 来源:尧图网络
1. 为什么我把 Java 错题本做成了 Swing 桌面面板刷 Java 基础选择题的时候很多人都有同一个痛点错题散落在纸质卷子、截图、备忘录里线程和 JDBC 这两块尤其容易反复错。线程状态转换、synchronized锁对象、PreparedStatement与Statement的区别、ResultSet游标方法这些知识点一旦混在一起光靠翻答案根本记不住。我试过用 Excel 记录结果每次复盘都要手动筛选也试过在线题库但题目顺序固定错题不能按知识点分组。后来干脆用 Swing 写了一个本地自测面板把线程、JDBC 相关的错题按知识点分组展示答完自动记录结果并统计正确率。整个程序不依赖网络双击就能跑适合放在桌面当日常复盘工具。这个面板能做什么简单说三件事第一从本地文件或数据库读取题目按「线程」「JDBC」「集合」「异常」等标签分组第二用单选按钮渲染选项点击「提交」后立即判断对错并更新统计第三把每次作答结果写入文件下次打开能看到历史正确率。适合谁正在准备 Java 基础考试、面试或者想系统梳理线程与 JDBC 知识点的同学。下面我会给出完整的 Swing 界面骨架、线程安全的答题状态更新写法以及 JDBC 错题数据读取配置。代码可以直接复制运行你只需要准备一个 MySQL 库或者用我提供的本地文件模式。2. 前置准备TaoToken 接入与开发环境配置在开始写 Swing 面板之前先说明一下模型调用部分。这个自测面板本身不依赖大模型但如果你想在答错时自动生成解析可以接入 TaoToken 的 API。TaoToken 是一个兼容 OpenAI 接口规范的模型调用平台官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。接入方式很简单注册后在控制台创建一个 API Key然后在代码里把 Base URL 指向https://taotoken.net/apiModel ID 填你需要的模型名称即可。如果你只是想做本地错题面板这一步可以跳过如果想加「错题解析」功能就继续往下看。开发环境方面你需要 JDK 8 或以上我用的 JDK 17。IDE 用 IntelliJ IDEA 或 Eclipse 都行。数据库部分我用 MySQL 8.0 存错题如果你不想装数据库可以用 SQLite 或者直接读 JSON 文件。下面给出 MySQL 的建表语句和 JDBC 配置。先建一个错题表CREATE TABLE java_quiz ( id INT PRIMARY KEY AUTO_INCREMENT, category VARCHAR(50) NOT NULL, question TEXT NOT NULL, option_a VARCHAR(255), option_b VARCHAR(255), option_c VARCHAR(255), option_d VARCHAR(255), answer CHAR(1) NOT NULL, explanation TEXT ); INSERT INTO java_quiz (category, question, option_a, option_b, option_c, option_d, answer, explanation) VALUES (线程, 线程调用了 sleep() 方法后该线程将进入 状态。, 可运行状态, 运行状态, 阻塞状态, 终止状态, C, sleep() 会让线程进入 TIMED_WAITING 状态属于阻塞的一种。), (线程, 线程控制方法中yield() 的作用是, 返回当前线程的引用, 使比其低的优先级线程执行, 强行终止线程, 只让给同优先级线程运行, D, yield() 让出 CPU但只让给同优先级的线程。), (JDBC, JDBC 中要显式地关闭连接的命令是, Connection.close(), RecordSet.close(), Connection.stop(), Connection.release(), A, 关闭连接用 Connection.close()。), (JDBC, 执行预编译的 SQL用, CallableStatement, Statement, PreparedStatement, CallStatment, C, PreparedStatement 用于预编译 SQL。), (JDBC, 接口 Statement 中定义的 executeQuery 方法返回的类型是, ResultSet, int, boolean, String, A, executeQuery 返回 ResultSet。);如果你用 TaoToken 做解析生成可以在explanation为空时调用模型补全。配置片段如下{ baseUrl: https://taotoken.net/api, apiKey: 你的_API_Key, model: gpt-4o-mini, timeout: 30 }注意API Key 不要硬编码在代码里放到环境变量或本地配置文件避免泄露。TaoToken 的 API Key 可以在控制台的 API Keys 页面创建地址是 https://taotoken.net/api-keys 。3. 可复制配置Swing 界面骨架与线程安全答题状态这一节给出完整的 Swing 界面代码。核心思路是主窗口用BorderLayout左侧是知识点分组列表右侧是题目面板底部是统计栏。答题状态用AtomicInteger和ConcurrentHashMap管理避免多线程更新时出现竞态。先看界面骨架import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; public class QuizPanel extends JFrame { private final JListString categoryList; private final DefaultListModelString categoryModel; private final JPanel questionPanel; private final JLabel statsLabel; private final JButton submitBtn; private final JButton nextBtn; // 线程安全的答题状态 private final ConcurrentHashMapInteger, String userAnswers new ConcurrentHashMap(); private final AtomicInteger correctCount new AtomicInteger(0); private final AtomicInteger totalCount new AtomicInteger(0); private ListQuizItem currentQuestions; private int currentIndex 0; private ButtonGroup optionGroup; public QuizPanel() { setTitle(Java 基础错题自测面板); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(900, 600); setLocationRelativeTo(null); setLayout(new BorderLayout(10, 10)); // 左侧分类列表 categoryModel new DefaultListModel(); categoryModel.addElement(全部); categoryModel.addElement(线程); categoryModel.addElement(JDBC); categoryList new JList(categoryModel); categoryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); categoryList.setBorder(new EmptyBorder(10, 10, 10, 10)); categoryList.addListSelectionListener(e - { if (!e.getValueIsAdjusting()) { loadQuestions(categoryList.getSelectedValue()); } }); JScrollPane leftScroll new JScrollPane(categoryList); leftScroll.setPreferredSize(new Dimension(150, 0)); add(leftScroll, BorderLayout.WEST); // 右侧题目区 questionPanel new JPanel(); questionPanel.setLayout(new BoxLayout(questionPanel, BoxLayout.Y_AXIS)); questionPanel.setBorder(new EmptyBorder(20, 20, 20, 20)); add(new JScrollPane(questionPanel), BorderLayout.CENTER); // 底部统计与按钮 JPanel bottomPanel new JPanel(new BorderLayout()); statsLabel new JLabel(正确率0 / 0); statsLabel.setBorder(new EmptyBorder(5, 10, 5, 10)); bottomPanel.add(statsLabel, BorderLayout.WEST); JPanel btnPanel new JPanel(new FlowLayout(FlowLayout.RIGHT)); submitBtn new JButton(提交答案); nextBtn new JButton(下一题); btnPanel.add(submitBtn); btnPanel.add(nextBtn); bottomPanel.add(btnPanel, BorderLayout.EAST); add(bottomPanel, BorderLayout.SOUTH); // 事件绑定 submitBtn.addActionListener(e - submitAnswer()); nextBtn.addActionListener(e - nextQuestion()); // 默认加载全部 categoryList.setSelectedIndex(0); } private void loadQuestions(String category) { // 这里从数据库或文件读取后面 JDBC 部分会实现 currentQuestions QuizRepository.findByCategory(category); currentIndex 0; userAnswers.clear(); correctCount.set(0); totalCount.set(0); renderQuestion(); } private void renderQuestion() { questionPanel.removeAll(); if (currentQuestions null || currentQuestions.isEmpty()) { questionPanel.add(new JLabel(暂无题目)); questionPanel.revalidate(); questionPanel.repaint(); return; } QuizItem item currentQuestions.get(currentIndex); JLabel qLabel new JLabel(htmlb (currentIndex 1) . item.getQuestion() /b/html); qLabel.setAlignmentX(Component.LEFT_ALIGNMENT); questionPanel.add(qLabel); questionPanel.add(Box.createVerticalStrut(10)); optionGroup new ButtonGroup(); String[] options {item.getOptionA(), item.getOptionB(), item.getOptionC(), item.getOptionD()}; char[] labels {A, B, C, D}; for (int i 0; i options.length; i) { JRadioButton rb new JRadioButton(labels[i] . options[i]); rb.setActionCommand(String.valueOf(labels[i])); rb.setAlignmentX(Component.LEFT_ALIGNMENT); optionGroup.add(rb); questionPanel.add(rb); } questionPanel.add(Box.createVerticalStrut(10)); JLabel resultLabel new JLabel( ); resultLabel.setName(resultLabel); resultLabel.setAlignmentX(Component.LEFT_ALIGNMENT); questionPanel.add(resultLabel); questionPanel.revalidate(); questionPanel.repaint(); updateStats(); } private void submitAnswer() { if (optionGroup null || optionGroup.getSelection() null) { JOptionPane.showMessageDialog(this, 请先选择一个答案); return; } QuizItem item currentQuestions.get(currentIndex); String selected optionGroup.getSelection().getActionCommand(); userAnswers.put(item.getId(), selected); totalCount.incrementAndGet(); if (selected.equals(item.getAnswer())) { correctCount.incrementAndGet(); } // 显示结果 for (Component c : questionPanel.getComponents()) { if (c instanceof JLabel resultLabel.equals(c.getName())) { boolean ok selected.equals(item.getAnswer()); ((JLabel) c).setText(ok ? 回答正确 : 回答错误正确答案 item.getAnswer()); ((JLabel) c).setForeground(ok ? new Color(0, 128, 0) : Color.RED); } } updateStats(); } private void nextQuestion() { if (currentQuestions null || currentQuestions.isEmpty()) return; if (currentIndex currentQuestions.size() - 1) { currentIndex; renderQuestion(); } else { JOptionPane.showMessageDialog(this, 已经是最后一题了); } } private void updateStats() { int total totalCount.get(); int correct correctCount.get(); double rate total 0 ? 0 : (correct * 100.0 / total); statsLabel.setText(String.format(正确率%d / %d (%.1f%%), correct, total, rate)); } public static void main(String[] args) { SwingUtilities.invokeLater(() - new QuizPanel().setVisible(true)); } }上面代码里ConcurrentHashMap和AtomicInteger保证了即使你在后台线程更新答题状态界面统计也不会错乱。SwingUtilities.invokeLater确保界面在事件调度线程中创建。接下来是数据模型和仓库类public class QuizItem { private int id; private String category; private String question; private String optionA; private String optionB; private String optionC; private String optionD; private String answer; private String explanation; // getter/setter 省略实际使用时补全 public int getId() { return id; } public String getCategory() { return category; } public String getQuestion() { return question; } public String getOptionA() { return optionA; } public String getOptionB() { return optionB; } public String getOptionC() { return optionC; } public String getOptionD() { return optionD; } public String getAnswer() { return answer; } public String getExplanation() { return explanation; } }JDBC 读取配置import java.sql.*; import java.util.ArrayList; import java.util.List; public class QuizRepository { private static final String URL jdbc:mysql://localhost:3306/java_quiz?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD 你的密码; public static ListQuizItem findByCategory(String category) { ListQuizItem list new ArrayList(); String sql 全部.equals(category) ? SELECT * FROM java_quiz : SELECT * FROM java_quiz WHERE category ?; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps conn.prepareStatement(sql)) { if (!全部.equals(category)) { ps.setString(1, category); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { QuizItem item new QuizItem(); item.setId(rs.getInt(id)); item.setCategory(rs.getString(category)); item.setQuestion(rs.getString(question)); item.setOptionA(rs.getString(option_a)); item.setOptionB(rs.getString(option_b)); item.setOptionC(rs.getString(option_c)); item.setOptionD(rs.getString(option_d)); item.setAnswer(rs.getString(answer)); item.setExplanation(rs.getString(explanation)); list.add(item); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }如果你用 TaoToken 生成解析可以在explanation为空时调用public static String generateExplanation(String question, String answer) { // 调用 TaoToken APIBase URL: https://taotoken.net/api // 具体 HTTP 调用代码略参考官方文档 return 解析生成中...; }注意JDBC 连接串里的useSSLfalse和serverTimezoneUTC根据你的 MySQL 版本调整。MySQL 8.0 需要加allowPublicKeyRetrievaltrue如果遇到公钥检索错误。4. 验证请求运行面板并确认题目加载与正确率统计代码写完后按以下步骤验证。第一步确认 MySQL 服务已启动并且java_quiz库和java_quiz表已创建。执行SELECT COUNT(*) FROM java_quiz;应该能看到至少 5 条记录。第二步把 MySQL 驱动 jar 包加入项目依赖。Maven 项目加dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency非 Maven 项目就手动下载 jar 并添加到 classpath。第三步运行QuizPanel.main()。窗口应该正常弹出左侧显示「全部」「线程」「JDBC」三个分类。点击「线程」右侧应该加载出两道线程题点击「JDBC」加载出三道 JDBC 题。第四步选择一道题点一个选项点「提交答案」。底部统计栏应该从「正确率0 / 0」变成「正确率1 / 1 (100.0%)」或「正确率0 / 1 (0.0%)」同时题目下方显示对错提示。第五步点「下一题」切换到下一道题选项重置结果标签清空。答完所有题后统计栏显示总正确率。如果你接入 TaoToken 做解析可以在提交后调用模型接口把返回的解析文本追加到结果标签下方。验证时注意 API Key 是否正确Base URL 是否为https://taotoken.net/api。模型对话功能可以在 https://taotoken.net/model-chat 测试确认 Key 有效后再集成到代码里。实测下来整个面板从启动到加载题目大约 1 秒切换分类无卡顿。如果你题目量很大比如上千道建议在loadQuestions里加一个后台线程用SwingWorker异步加载避免界面冻结。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节整理我在开发和接入过程中遇到的真实报错以及排查方法。报错一401 Unauthorized如果你调用 TaoToken API 时返回 401说明 API Key 无效或未正确传递。检查请求头connection.setRequestProperty(Authorization, Bearer apiKey);确认apiKey没有多余空格并且是在 https://taotoken.net/api-keys 创建的。如果 Key 刚创建等几秒再试。报错二local proxy failed这个报错通常出现在你本地设置了代理但代理不可用。检查系统环境变量HTTP_PROXY和HTTPS_PROXY如果不需要代理就清空。Java 代码里可以用System.setProperty(http.proxyHost, ); System.setProperty(http.proxyPort, );或者直接在 JVM 启动参数加-Dhttp.proxyHost -Dhttp.proxyPort。报错三reading choices这个报错一般出现在解析模型返回的 JSON 时字段名不匹配。比如你期望choices[0].message.content但实际返回结构不同。建议先打印完整响应体System.out.println(responseBody);确认字段路径后再解析。TaoToken 兼容 OpenAI 格式正常返回应该包含choices数组。报错四OAuth 相关错误如果你用 OAuth 方式接入报错可能是 token 过期或 scope 不足。检查 token 有效期重新生成。如果只是本地自测面板建议直接用 API Key不用 OAuth。报错五JDBC 连接失败Communications link failure说明 MySQL 没启动或端口不对。Access denied for user说明用户名密码错误。Unknown database说明库没建。逐个排查即可。报错六Swing 界面乱码如果中文显示为方块在 JVM 启动参数加-Dfile.encodingUTF-8或者用new JLabel(new String(中文.getBytes(UTF-8), UTF-8))。推荐统一用 UTF-8 编码。报错七线程安全统计错误如果你在后台线程直接更新JLabel可能抛NullPointerException或界面不刷新。所有界面更新必须放在SwingUtilities.invokeLater里SwingUtilities.invokeLater(() - statsLabel.setText(...));记住Swing 组件不是线程安全的只有事件调度线程能操作它们。6. 下一步把错题面板接入 Coding Plan 做长期复盘这个 Swing 面板已经能完成基本的错题分组、作答和统计。如果你想进一步自动化比如每天自动拉取新题、生成解析、记录历史趋势可以接入 TaoToken 的 Coding Plan。Coding Plan 适合长期编码和 Agent 场景地址是 https://taotoken.net/coding-plan 。具体做法写一个定时任务每天从数据库读取未掌握的错题调用模型生成解析然后把解析写回explanation字段。下次打开面板时错题旁边就会显示详细解析。模型调用部分参考接入文档 https://taotoken.net/doc 里面有完整的请求示例和参数说明。如果你更想先验证模型效果可以到模型对话页面 https://taotoken.net/model-chat 手动测试几道线程和 JDBC 题目确认解析质量后再集成。控制台 https://taotoken.net/console 可以查看调用量和余额。最后提醒一点错题面板的核心价值在于「反复练」而不是「一次做完」。建议把答错的题目标记为「待复习」下次打开时优先加载这些题。你可以在java_quiz表加一个wrong_count字段每次答错就加一加载时按wrong_count DESC排序。这样坚持一周线程和 JDBC 的高频错点自然就攻克了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →