Java Swing实现随机出题与自动批改的口算练习小程序

发布时间:2026/10/23 16:14:17
Java Swing实现随机出题与自动批改的口算练习小程序

简介:这是一份面向 Java 初学者的 GUI 教育项目完整源码,基于 Swing/JavaFX 技术实现算术题随机生成与在线练习批改,适用于课程设计、期末实训或自学练手场景。压缩包共 55 个文件,包含 17 个 Java 源文件、33 个 class 编译产物、2 个 csv 题库文件以及 Eclipse 工程配置,整体仅 75KB,目录结构清晰,便于直接导入开发环境运行与学习。已有 1474 人学习下载,属于上手门槛较低、完成度较高的实践项目;代码按 Controller、View、Model 三层组织,覆盖随机出题、难度参数设置、题库导入导出、数据库持久化、异常处理等模块。csv 文件中预置了算式基础数据,class 文件可立即运行演示,方便先看效果再对照源码学习。通过研读源码,读者可以理解 GUI 事件驱动机制、文件读写与 JDBC 数据库交互,以及如何将界面、业务逻辑与数据分层解耦,是锻炼 Java 综合编程能力、快速搭建完整小项目的良好参考。

1. JavaGUI 实现产生算术题并在线练习批改:一个能现场出题又能自动判分的桌面小程序

朋友家孩子每天要做一页口算题,家长出题十分钟、批改五分钟,孩子写题只要三分钟——时间全耗在出题和改题上。这份 javaGUI 技术实现的桌面小程序就是冲这个痛点来的:启动后选年级、选运算符、点开始,屏幕上自动出题并倒计时,交卷后立刻显示得分和错题列表,错题还能导出成 CSV 二次复习。对刚学 Java GUI 的开发者,它是一个结构完整的 Swing 练手项目;对带娃做口算的家长,它是一个不用联网、不用装数据库的轻量练习工具。整个工程没有花哨界面,但「出题—作答—批改—记录」这条链路是闭环的,跑起来就知道它比想象中省事得多。

2. 技术栈与目录结构:从 rar 压缩包到可运行的 Swing 工程

拿到这份 rar 资源后,别急着双击,先看里面装的是什么。这个项目属于典型的 JDK 自带 API 工程,压缩包解压后没有复杂的 Maven 依赖树,核心就是一个基于 Swing 的 GUI 程序加几个工具类,这在课程设计里是最常见的形态。

2.1 技术选型:为什么用 Swing 而不是 JavaFX

打开压缩包后的第一感觉是工程很轻。没有第三方依赖,主界面全部基于 javax.swing 包,Swing 随 JDK 自带,装好 JDK 8 就能编译运行,不用额外引入模块化运行时。JavaFX 界面更现代,但要配 JavaFX SDK,还要处理模块导出问题,对练习批改这种单窗口场景多少有点杀鸡用牛刀。算数题生成和批改也用不上框架,整数四则运算直接用 long,涉及除法时换 BigDecimal,避免 0.1+0.2 这类浮点精度问题。数据持久化选的是 CSV 文本文件而不是数据库,学生做完题,老师直接用 Excel 打开就能二次编辑,这也是很多 Java 桌面项目里被验证过的做法。

这个工程对运行环境的要求很低:JDK 8 以上即可。用 JDK 17 打开也能正常编译,只要代码里不碰 com.sun.* 的内部类就没有兼容性问题。作者刻意控制了依赖体积,对新手来说最大的好处是:源码里没有任何一个类需要你额外去网上下载 jar 包,解压即读。

2.2 解压后的目录结构

解压后先扫一眼目录,我按常见形态整理了一份文件清单:

路径作用关键点
src/com/study/math/Main.java程序入口,包含 main 方法右键直接运行的起点
src/com/study/math/generator/ArithmeticGenerator.java随机出题去重与整除处理的核心
src/com/study/math/ui/MainFrame.java主窗口负责切换练习面板和成绩面板
src/com/study/math/ui/PracticePanel.java练习面板计时、答题输入、交卷按钮
src/com/study/math/checker/AnswerChecker.java批改判分BigDecimal 容错,判分逻辑都在这
src/com/study/math/store/RecordStore.java结果落盘把成绩和错题写成 CSV
config.properties出题范围、运算符、练习时长不重新编译也能调参
docs/使用说明和接口说明运行步骤优先看这里

不同分发版本的类名可能略有出入,但大致结构不会跑出这个骨架。压缩包里如果没有 lib 目录,说明整个工程完全靠 JDK 自带 API 工作;如果带了 lib,通常也只是放了一个日志包或打包插件,不影响主线逻辑。

2.3 构建与运行:两种启动方式

方式一是 IDE 导入。解压后用 IntelliJ IDEA 的 File → Open 直接选根目录,确认 SDK 是 JDK 8 以上,如果识别到 Maven 或 Gradle 就等索引完成;识别不到就当普通工程打开。找到 Main.java 右键 Run,第一次运行前记得检查 Project Structure 里的 Language Level,建议设为 8 或对应 JDK 版本。

方式二是命令行编译运行。在项目根目录执行:

javac -encoding UTF-8 -d out src/com/study/math/**/*.java java -Dfile.encoding=UTF-8 -cp out com.study.math.Main

第一条命令把源码按 UTF-8 编码编译到 out 目录,源码里如果有中文字符串,少了 -encoding 参数就会出现乱码或编译报错;第二条命令里的 -Dfile.encoding=UTF-8 让 JVM 运行时也按 UTF-8 解析资源文件和控制台输出。这两个参数是这个项目能不能跑顺的关键,后面避坑章节还要专门展开。

如果根目录已经带了编译好的 out 目录,直接执行java -cp out com.study.math.Main就能启动。想打包成可双击的 jar,常见做法是建一个 MANIFEST.MF 指定 Main-Class,再用 jar cfm 命令打包,这些细节在 docs 目录里通常会写。

2.4 config.properties 里的参数说明

项目不用重新编译就能调整练习参数,靠的是根目录下的 config.properties:

range.min=1 range.max=99 operators=+,-,* practice.count=20 exam.duration=300

range.min 和 range.max 决定随机操作数的取值范围,operators 是允许出现的运算符列表,practice.count 控制每套练习题量,exam.duration 是整个练习的倒计时秒数。把 max 改成 1000 就能出三位数运算,把 duration 改成 120 就变成两分钟限时练习。配置文件是启动时一次性加载的,不会热更新,改完参数要重启程序才能生效,这一点在二次开发时要牢记。

3. 核心模块拆解:一道算术题从生成到判分的完整链路

这一章是整个项目最值得读的部分。我把「出题 → 展示 → 作答 → 判分 → 落盘」这条链路拆成四段,每段都给出核心代码和参数说明,读完你就能照着改。

3.1 随机出题:ArithmeticGenerator 的核心方法

题目生成器是整套系统最前面的环节。它的职责不是简单拼一个字符串,而是要保证题目符合小学口算的规则:减法不能出现负数,除法必须整除,同一套练习里不要出现重复题。

public class ArithmeticGenerator { private final int min; private final int max; private final String[] operators; private final Set<String> usedQuestions = new HashSet<>(); public ArithmeticGenerator(int min, int max, String[] operators) { this.min = min; this.max = max; this.operators = operators; } public String nextQuestion() { String question; do { question = createQuestion(); } while (usedQuestions.contains(question)); usedQuestions.add(question); return question; } private String createQuestion() { String op = operators[ThreadLocalRandom.current().nextInt(operators.length)]; int a = randomOperand(); int b = randomOperand(); if ("-".equals(op) && a < b) { int t = a; a = b; b = t; } if ("/".equals(op)) { b = randomOperand(); if (b == 0) b = 1; a = b * randomOperand(); } return a + " " + op + " " + b + " ="; } private int randomOperand() { return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } }

createQuestion 先随机运算符再随机操作数。减法里 a < b 时交换两数,保证结果永远非负;除法里先随机除数 b,再让被除数 a = b × randomOperand(),这样商一定是整数,从源头避免了「7 ÷ 2 = 3.5」这种不该出现在口算题里的情况。Set 集合 usedQuestions 负责去重,同一套练习内不会连续出现同一道题。

参数说明:min 和 max 是操作数的闭区间边界,控制数字难度;operators 数组决定这套练习考哪种运算;每次 new ArithmeticGenerator 都会清空去重记录,如果希望多套练习之间也不重复,需要把这个类提升为单例。

3.2 练习面板:Swing Timer 与界面状态切换

题面生成之后,由 PracticePanel 负责展示和收集输入。这里最核心的机制是 Swing 的 Timer 组件,它每秒钟触发一次倒计时,既能给用户实时反馈,又能在时间耗尽时自动交卷。

public class PracticePanel extends JPanel { private final ArithmeticGenerator generator; private final JLabel questionLabel; private final JTextField answerField; private final Timer timer; private int remainingSeconds; private final int totalQuestions; private int currentIndex; private final List<String> answers = new ArrayList<>(); public PracticePanel(int questionCount, int durationSeconds) { this.generator = new ArithmeticGenerator(1, 99, new String[]{"+", "-", "*"}); this.totalQuestions = questionCount; this.remainingSeconds = durationSeconds; this.timer = new Timer(1000, e -> tick()); // 界面布局初始化略 } private void tick() { remainingSeconds--; if (remainingSeconds <= 0) { timer.stop(); submit(); // 时间到,自动交卷 } timeLabel.setText("剩余 " + remainingSeconds + " 秒"); } private void showNext() { questionLabel.setText(generator.nextQuestion()); answerField.setText(""); currentIndex++; } }

Timer 的第一个参数 1000 表示每 1000 毫秒触发一次 tick,第二个参数是事件回调。tick 里把 remainingSeconds 减一,归零时立即停表并调用 submit,避免用户一直不点交卷导致练习无限期挂起。currentIndex 记录当前做到第几题,answers 列表按顺序保存用户每次输入,交卷时交给批改模块。

这里有个细节要注意:Swing Timer 的回调运行在 EDT 线程,不要在 tick 里做文件读写或大批量生成题目的耗时操作,否则界面会卡住,这个问题在第 4 章的避坑记录里还会遇到。

3.3 批改判分:AnswerChecker 的容错设计

判分模块决定了系统是不是「真能用」,而不是「看着能用」。家长手动批改时最烦的几种情况,这里都用代码做了处理。

public class AnswerChecker { public static BigDecimal parseAnswer(String input) { if (input == null || input.trim().isEmpty()) { return null; // 空答案按未作答处理 } try { return new BigDecimal(input.trim()); } catch (NumberFormatException e) { return null; // 非数字内容按未作答处理 } } public static boolean check(String question, String answer) { BigDecimal user = parseAnswer(answer); if (user == null) return false; BigDecimal correct = evaluate(question.replace("=", "").trim()); if (correct == null) return false; return user.stripTrailingZeros().compareTo(correct.stripTrailingZeros()) == 0; } }

parseAnswer 做了两道防线:空字符串和非法输入都返回 null,null 在 check 里直接判错,不会抛异常。evaluate 方法把「12 + 8 =」这种题面字符串转成后缀表达式求值,内部运算全部用 BigDecimal,杜绝了 double 计算带来的精度误差。

最容易被忽略的是最后一行的 stripTrailingZeros().compareTo()。学生写「3.0」和写「3」,在数学上是一个答案,但如果用 Double 的 == 比较,3.0 和 3 在部分边界场景下会判不一致。用 BigDecimal 的 compareTo 比较数值,再配合去掉末尾零,就能把这种写法差异吞掉。

参数说明:question 是形如 "12 + 8 =" 的题面,answer 是用户在输入框里填的内容。答案带单位、写中文数字、漏写等号的都会被归为未作答,错题记录里一眼就能看出是没答还是答错,这对老师整理学情很有用。

3.4 错题落盘:RecordStore 写 CSV

批改完成后,结果不能只显示在屏幕上,还要能存下来。RecordStore 把每个学生的练习结果导出成独立的 CSV 文件,按题号排列,方便导入 Excel。

public class RecordStore { public void saveResult(String studentName, int score, List<String> questions, List<String> answers) throws IOException { Path path = Paths.get("records", studentName + ".csv"); Files.createDirectories(path.getParent()); List<String> lines = new ArrayList<>(); lines.add("题号,题目,我的答案,状态"); for (int i = 0; i < questions.size(); i++) { boolean correct = AnswerChecker.check(questions.get(i), answers.get(i)); String status = correct ? "正确" : "错误"; lines.add((i + 1) + "," + questions.get(i) + "," + answers.get(i) + "," + status); } Files.write(path, lines, StandardCharsets.UTF_8); } }

Files.createDirectories 会自动创建 records 目录,不用担心首次运行报「目录不存在」。每行固定四个字段,用逗号分隔,CSV 里就没有乱列的问题。score 参数记录了总分,但写入文件时只写了明细,总分在上方 UI 的成绩面板展示,这里保留参数是为了后续扩展时直接追加字段。

参数说明:studentName 作为文件名的一部分,写入前要做非法字符过滤,不能包含 / \ : 等符号,否则会报 IOException。题目和答案字段如果本身包含逗号会被 Excel 拆列,但这个项目的题面格式是 "12 + 8 =",不会出现逗号,所以没做转义处理,属于够用就好的取舍。

4. 避坑与常见问题排查:五个让项目跑不起来的经典翻车现场

这个 Swing 项目整体不难,但运行环境、字符编码、随机算法这几类问题几乎每个拿到源码的人都会踩一遍。下面五条是从实际使用中筛出来的高频问题,按「现象 → 原因 → 解决」整理。

4.1 中文乱码:按钮和题目全是问号

现象:程序能启动,但界面上所有中文按钮、标签、题目全部显示为「????」或乱码。

原因:源码文件本身是 UTF-8 编码,但 javac 在命令行编译时默认按操作系统的平台字符集解析,在中文 Windows 上就会把 UTF-8 的中文按 GBK 读,直接编译错位或者运行时输出乱码。IDE 里出现这问题通常是 Project Encoding 没统一。

解决:IDE 里把 File Encodings 里的 Global、Project、Properties Files 全设成 UTF-8;命令行编译时强制加上-encoding UTF-8,运行时的参数也要带-Dfile.encoding=UTF-8。这两条同时做,乱码基本绝迹。

4.2 高分屏界面模糊且文字特别小

现象:在 2K 或 4K 分辨率的 Windows 笔记本上运行,整个窗口缩在一角,文字发虚,按钮挤压变形。

原因:JDK 8 对高分屏的 DPI 缩放支持不完整,它在高分辨率下默认按 100% 缩放渲染,和 Windows 的显示缩放比例对不上,就会出现模糊。

解决:最省事的办法是改用 JDK 11 或更高版本打开工程,新版对高分屏适配好很多;不想换 JDK 就在启动脚本里加-Dsun.java2d.dpiaware=true,或者右键运行的 exe/jar 在兼容性设置里选「系统(增强)」缩放。这是环境问题不是代码问题,不用改源码。

4.3 除法题除不尽

现象:开始练习后题面里出现了「7 ÷ 2 =」这种整除不了的式子,学生被迫写小数答案。

原因:生成器在设计随机表达式时,只随机了被除数和除数,并没有校验「a % b == 0」,或者校验逻辑写在了生成之后,一旦不合法就直接跳过题目,反复重试导致死循环。

解决:按 3.1 节的写法,先随机一个除数,再随机一个整数商,最后算出被除数 = 除数 × 商。这样生成的题目天然整除,不需要事后判断。如果你希望保留小数题型,就在 config.properties 里加一个 allowDecimal 开关,两种模式并存,按用户选择出题。

4.4 答案「3.0」被判错

现象:学生把 3 写成 3.0,系统直接打红叉,家长还以为判分逻辑写错了。

原因:判分模块用了 Double.parseDouble 再拿 == 比较,double 在比较某些小数时会因为二进制浮点表示产生误差,3.0 和 3 在这种比较逻辑下是可能被当成不同值的。

解决:改成 BigDecimal,比较时先 stripTrailingZeros() 去掉末尾冗余的零,再 compareTo 比较数值大小,而不是用 equals 比较。这样 3.0、3.00、3 都能归一判对,同时保留 0.1 + 0.2 这类场景的精度。

4.5 点击「开始练习」后窗口假死

现象:点了开始按钮,窗口直接变成「未响应」,过好几秒才恢复,或者一直卡住。

原因:在按钮的 ActionListener 里直接循环生成几百道题,或者去重逻辑死循环,把 UI 线程阻塞了。Swing 的所有操作都在 EDT 线程里,耗时操作占住线程,界面自然就卡死。

解决:把题目生成放到 SwingWorker 的 doInBackground 里,生成完再回 UI 线程刷新界面;或者学习 3.1 节的做法,在界面进入练习页之前就把题目池预生成好,用户答题时只是按顺序取题,永远不触发随机数生成。这两个方案任选其一,假死问题就能从根上解决。

5. 二次开发实战:把单机口算工具改造成可配置的练习平台

下载这份 rar 资源后,最快十分钟就能把界面跑起来,但真正有价值的是下一步——按自己的需求改。这里给出三条实际可操作的改造思路和验证方法。

5.1 改造点一:把运算符配置做成年级模板

现在 config.properties 里的 operators 是写死的字符串,不同年级要手动改文件重启。更优雅的做法是引入一个 Grade 枚举,每种年级对应一组出题参数:

public enum Grade { GRADE_1(1, 20, new String[]{"+", "-"}), GRADE_2(1, 99, new String[]{"+", "-", "*"}), GRADE_3(1, 999, new String[]{"+", "-", "*", "/"}); private final int min; private final int max; private final String[] operators; Grade(int min, int max, String[] operators) { this.min = min; this.max = max; this.operators = operators; } public ArithmeticGenerator createGenerator() { return new ArithmeticGenerator(min, max, operators); } }

主界面上加一个 JComboBox 让用户选年级,选中后直接调用 grade.createGenerator() 生成新的出题器,比手动改配置文件直观得多。参数说明:min 和 max 是闭区间边界,operators 数组传给生成器后,除法题同样走整除约束,三个年级共用一套校验逻辑,不会出现二年级出三位数乘法这种不合理的题。

5.2 改造点二:把批改引擎和 UI 彻底解耦

现在 AnswerChecker 是一个静态工具类,可以直接拿来做单元测试,不用启动界面。改造时建议给它加一个 main 方法做冒烟验证:

public static void main(String[] args) { ArithmeticGenerator generator = new ArithmeticGenerator(1, 99, new String[]{"+", "-", "*", "/"}); for (int i = 0; i < 1000; i++) { String question = generator.nextQuestion(); boolean valid = question.matches("\\d+ [\\+\\-\\*\\/] \\d+ ="); if (!valid) { throw new AssertionError("非法题面: " + question); } } System.out.println("1000 道题全部通过格式校验"); }

这段代码反复生成 1000 道题并校验题面格式,跑完没有任何输出异常说明随机算法稳定。以后每次改完生成器,先跑一遍这个验证方法,比直接启动界面截图检查快得多。这是我从这次改造里学到的习惯:纯逻辑模块一定要能独立运行验证,不要每次都得等 Swing 窗口加载完。

5.3 改造后的验证清单

改完代码不要急着提交,按下面这套流程过一遍:第一,分别切到一、二、三年级模板,各生成一套题,肉眼确认减法没有负数、除法没有小数;第二,用「3.0」「3」「03」三种写法做判分测试,确认三种都判对;第三,导出 CSV 后用 Excel 打开,检查分列是否正确乱码是否存在;第四,在高 DPI 屏幕上跑一次,看界面是否缩放正常。如果这四项全过,这套改造就可以放心拿去给学生用了。

那次改造之后,我每次拿到一个 rar 形式的 Java 工程,都强制自己先走一遍「看目录 → 跑主类 → 改配置 → 跑验证方法」的流程,而不是直接打开源码就动手删改。这个习惯帮我避掉了不少低级问题。希望帮到你。

本文还有配套的精品资源,点击获取