Java GUI实战:Swing实现算术题生成与在线批改完整方案
简介这是一套基于Java GUI技术开发的算术题在线练习与批改项目适合Java初学者、学生或课程设计者用于理解图形界面编程、随机出题、文件读写与数据持久化。压缩包共55个文件主体包含33个class编译文件与17个java源文件另有2个csv题库数据文件以及project、classpath等工程配置文件既能直接运行查看效果也便于导入IDE对照源码学习。项目采用Controller、View、Model分层结构完整实现随机加减法出题、题目数量与数字范围难度设置、在线提交答案、即时批改与错误提示并支持题库从CSV导入、导出以及通过JDBC进行数据持久化附带异常处理与测试调试思路。已有1472人浏览、学习包体仅75KB却涵盖源码、编译产物与配置信息尤其适合需要快速上手Java Swing或JavaFX综合应用、参考完整可运行教育小项目的开发者。借助清晰的工程结构还可作为课程设计或期末项目的参考模板。1. “javaGUI实现产生算术题并在线练习批改”里真正难的不是GUI把“javaGUI实现产生算术题并在线练习批改”这个标题拆开看Java GUI只是外壳算术题生成和在线批改才是决定这个程序“像不像样”的内核。这类项目常见于Java课程设计或培训机构阶段作业需求看似简单生成题、展示、输入答案、判断对错。但实际做起来随机数范围、除法整除、负数边界、全角输入、重复题和答案字符串比对都会变成坑界面反而只是层层叠组件。本文按“生成器 → Swing界面 → 批改引擎 → 踩坑排查 → 打包交付”顺序展开把每个环节的边界条件和复用点讲清楚代码都按最小可运行来写。适合两类人刚学Swing、想拿完整项目练手的人以及要做课程设计但不想把项目写成“一个类从头写到尾”的人。正文里所有类名和目录结构都可以直接搬进自己的工程再改。2. 算术题生成器先定题面模型再谈随机与参数很多初学者会把生成器写成“new Random 然后拼字符串”结果代码越改越乱批改时又得重新解析题面。常见做法是先拆成三件事题面模型、生成规则、参数配置。这里每一步都直接影响后续界面和批改引擎的复杂度。2.1 题面模型和生成器接口把“出题”与“做题”解耦先定义一个最小的题面类。不管界面还是批改操作的都是同一份“题目对象”而不是一串到处传的String。public class ArithmeticQuestion { private final String expression; // 展示给用户的题面如 23 45 private final String answer; // 规范答案如 68 private final int difficulty; // 难度等级后续分级练习用 public ArithmeticQuestion(String expression, String answer, int difficulty) { this.expression expression; this.answer answer; this.difficulty difficulty; } public String getExpression() { return expression; } public String getAnswer() { return answer; } public int getDifficulty() { return difficulty; } }expression 直接交给 JLabel 展示answer 在批改阶段使用。注意 answer 用 String 而不是 double因为“6 ÷ 2”和“3.0”如果在批改时直接转浮点比较会出现格式不一致的麻烦String 保留规范写法批改时再统一处理。接下来定义一个生成器接口。这样做最大的好处是以后要加“混合运算”“带括号”“应用题”只需要替换实现类界面和批改引擎一行都不用改。public interface ArithmeticQuestionGenerator { ArithmeticQuestion next(); void reset(long seed); }在 Swing 项目里这个接口是少数能体现面向对象设计的地方也是课程设计答辩时容易加分的点。实际做的时候我会把接口单独放一个包实现类放另一个包这样代码结构清晰。2.2 难度参数、运算符权重与随机种子可复现的题海生成器最常被吐槽的就是“题太简单”“题太难”“减法全是负数”“除法除不尽”。常见做法是用一个配置类把所有参数集中管理避免在生成器里写死。public class GeneratorConfig { private int maxOperand 100; // 操作数最大值 private boolean allowNegative false; // 是否允许结果为负数 private boolean allowDivision false; // 是否启用除法 private int maxQuotient 9; // 除法允许的最大商 private long seed 0; // 0 表示不固定随机种子 private int[] operatorWeights {1, 1, 0}; // 加、减、乘权重 public GeneratorConfig() { } public void check() { if (maxOperand 0) throw new IllegalArgumentException(maxOperand必须大于0); if (operatorWeights.length 3) throw new IllegalArgumentException(至少要有加减乘三项权重); } }字段含义maxOperand 控制操作数范围100以内加减法适合口算20以内适合低年级allowNegative 控制减法结果是否允许负数allowDivision 和 maxQuotient 配合保证除法不出现“789 ÷ 3”这种没有训练价值的题operatorWeights 用权重数组而不是布尔开关方便以后调成“加法30%、减法30%、乘法20%、除法20%”。拿到权重数组后选择一个运算符的常用写法是前缀累积法比单纯 if-else 更好扩展import java.util.Random; private Operator pickOperator(Random random, GeneratorConfig config) { int[] weights config.getOperatorWeights(); int total 0; for (int weight : weights) total weight; int point random.nextInt(total); for (int i 0; i weights.length; i) { point - weights[i]; if (point 0) { return Operator.values()[i]; } } return Operator.ADD; // 兜底正常情况下不会到这 }这段代码的核心逻辑是“把一个随机数按权重比例切段”。比如权重是 2:2:1:1总数是6随机数落在哪一段就选哪个运算符。以后想调整难度只改配置不改生成逻辑。随机种子这一点经常被忽略。给 Random 一个固定种子可以让每次生成的题目序列完全一致这在排查问题时特别有用。比如某同学反馈“练到第100题时出现了一道负数减法”如果你固定了种子可以立刻复现同一道题如果不固定只能靠运气复现。生产运行时用System.currentTimeMillis()当种子调试时用固定值。2.3 负数与除法两个最容易翻车的生成细节减法生成最怕出现负数结果。如果你的配置要求不允许负数最简单可靠的做法不是“生成后循环重试”而是生成两个操作数后做交换case SUBTRACT: { int a random.nextInt(maxOperand 1); int b random.nextInt(maxOperand 1); if (!config.isAllowNegative() a b) { int temp a; a b; b temp; } return new ArithmeticQuestion(a - b, String.valueOf(a - b), 1); }交换法比 while 循环重试更可控没有性能红线也绝不会出现“重试一万次还生成不出来”的情况。如果 allowNegative 为 true就不交换但要明确告诉使用者这道题可能出负数。生成器里“宁可不随机也不要越界”是底线。除法生成最容易翻车很多人会先随机出被除数再随机出除数然后判断能不能整除不能就重试。这种做法在范围小的时候问题不大范围大了会明显变慢极端情况下可能死循环。更稳的是用乘法逆运算先定商和除数再算被除数。case DIVIDE: { int quotient random.nextInt(Math.max(1, config.getMaxQuotient())) 1; int divisor random.nextInt(Math.max(1, config.getMaxOperand() / 2)) 1; int dividend quotient * divisor; return new ArithmeticQuestion( dividend ÷ divisor , String.valueOf(quotient), 1 ); }这样生成的除法题一定整除除数一定不为0被除数不会超出配置范围。参数说明maxQuotient 控制商的位数maxOperand/2 控制除数范围避免被除数过大。如果以后要做“有余数除法”就在 dividend 上加一个小于 divisor 的随机余数把标准答案改成“商余余数”界面提示也要跟着改。写到这里建议顺手加一个批量自检入口用 main 方法生成 1000 道题断言每道题的表达式和答案都不为空并且答案数值在配置范围内。这个自检习惯在打包交付前能救回很多低级错误。3. Swing在线练习界面从布局到事件的完整落地这里的“在线”不是联网而是程序内部一轮完整的人机交互出题、等待输入、判断、下一题。标题里的“在线练习批改”本质上就是“即时反馈”不需要网络模块。界面部分我按最小可运行结构来写不引入复杂 MVC但会把状态从界面组件里拆出去。3.1 为什么这种练习工具选Swing而不是JavaFX很多人会纠结技术选型。算术题练习批改这种单窗口小工具选 Swing 的理由非常实在JDK 自带的 Swing 组件不需要额外运行时打包后体积小课程设计环境普遍兼容JavaFX 虽然界面更现代但需要模块系统或者 jlink 处理给使用者带来的环境成本更高。维度SwingJavaFX运行环境JDK 自带老版本环境也能跑需要 JavaFX 运行时界面样式UIManager 换肤偏桌面传统CSS 控制现代感更强打包复杂度直接打 jarMain-Class 声明即可需要模块描述或 jlink 定制适合场景小工具、课程设计、传统桌面程序需要图表、动效、复杂样式的中大型客户端对“出题 输入框 按钮 结果弹窗”这种需求Swing 完全够用。如果项目描述里明确写了“Java GUI”选 Swing 基本不会错。选型不是越高大上越好而是越符合交付场景越好。3.2 主界面最小可运行结构标签、输入框、按钮与键盘事件直接给一个可运行的 JFrame 类。里面包含进度提示、题面、输入框、提交按钮四块布局用 BorderLayout 和 GridLayout 组合去掉多余容器保证新手能看懂。public class PracticeFrame extends JFrame { private final ArithmeticQuestionGenerator generator; private final AnswerMarker marker; private final int questionCount; private ListArithmeticQuestion questions; private int currentIndex; private int correctCount; private final JLabel progressLabel new JLabel(准备中, SwingConstants.CENTER); private final JLabel questionLabel new JLabel(, SwingConstants.CENTER); private final JTextField answerField new JTextField(); private final JButton submitButton new JButton(提交并下一题); public PracticeFrame(ArithmeticQuestionGenerator generator, AnswerMarker marker, int questionCount) { this.generator generator; this.marker marker; this.questionCount questionCount; initUI(); startNewSet(); } private void initUI() { setTitle(算术题在线练习批改); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout(12, 12)); JPanel center new JPanel(new GridLayout(4, 1, 8, 8)); center.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); progressLabel.setFont(new Font(SansSerif, Font.BOLD, 16)); questionLabel.setFont(new Font(SansSerif, Font.PLAIN, 28)); answerField.setFont(new Font(SansSerif, Font.PLAIN, 20)); answerField.setHorizontalAlignment(JTextField.CENTER); submitButton.setFont(new Font(SansSerif, Font.PLAIN, 16)); center.add(progressLabel); center.add(questionLabel); center.add(answerField); center.add(submitButton); add(center, BorderLayout.CENTER); submitButton.addActionListener(e - submitCurrent()); answerField.addActionListener(e - submitCurrent()); setSize(480, 260); setLocationRelativeTo(null); } private void startNewSet() { questions new ArrayList(); for (int i 0; i questionCount; i) { questions.add(generator.next()); } currentIndex 0; correctCount 0; showQuestion(); } private void showQuestion() { ArithmeticQuestion question questions.get(currentIndex); progressLabel.setText(第 (currentIndex 1) / questionCount 题); questionLabel.setText(question.getExpression()); answerField.setText(); answerField.requestFocus(); } private void submitCurrent() { String input answerField.getText(); if (input.trim().isEmpty()) { answerField.requestFocus(); return; } boolean correct marker.check(input, questions.get(currentIndex)); if (correct) { correctCount; } currentIndex; if (currentIndex questionCount) { JOptionPane.showMessageDialog(this, 本次答对 correctCount / questionCount); int result JOptionPane.showConfirmDialog(this, 要不要再做一组, 继续练习, JOptionPane.YES_NO_OPTION); if (result JOptionPane.YES_OPTION) { startNewSet(); } else { dispose(); } } else { showQuestion(); } } }逻辑说明构造函数把生成器和批改器都从外部传入界面自己不 new Random这样测试时可以固定种子也可以把同一组题交给批改引擎做单元测试。事件部分做了两层绑定按钮点击可以提交输入框回车也可以提交这是桌面练习工具的常用交互习惯能减少一次鼠标操作。空输入直接 return不弹错误提示。这是因为练习场景里“不小心点了一下按钮”很常见弹窗反而打断节奏。把判空当成输入校验而不是题目判错这是产品细节。3.3 整卷练习还是单题即时反馈交互状态如何设计这个项目有两种常见交互模式一种是单题即时反馈答完立刻知道对错另一种是整卷批改全部做完再统一看成绩。标题“在线练习批改”更贴近第一种但我建议把状态抽出来两种模式都能支持。public class PracticeSession { private final ListArithmeticQuestion questions; private final ListArithmeticQuestion wrongList new ArrayList(); private int index; private int correctCount; public PracticeSession(ListArithmeticQuestion questions) { this.questions questions; } public ArithmeticQuestion current() { return questions.get(index); } public boolean hasNext() { return index questions.size() - 1; } public void answer(String input, AnswerMarker marker) { if (marker.check(input, current())) { correctCount; } else { wrongList.add(current()); } index; } public int getCorrectCount() { return correctCount; } public int getTotalCount() { return questions.size(); } public ListArithmeticQuestion getWrongList() { return wrongList; } }这个类的作用是把“题目进度、答对数量、错题集合”统一管理界面只负责显示和收集输入批改逻辑不掺在按钮事件里。PracticeFrame 里的 submitCurrent 可以直接改为调用 session.answer(input, marker)然后判断 session.hasNext() 决定是显示下一题还是弹成绩。如果以后想改成整卷模式只需要把“不调用 marker.check只在最后统一批改”的逻辑再包一层。状态集中管理最大的收益是可测试不用启动 Swing 窗口直接 new PracticeSession传入模拟题目和输入就能验证批改逻辑。4. 批改引擎与成绩统计字符串比对只是第一步批改引擎是这个项目里最容易“看起来简单、做着翻车”的部分。直接比较字符串只能处理最理想的情况一旦用户输入带空格、全角符号、多余括号或者给出等价表达式字符串相等就失效了。这一章讲清楚输入清洗、表达式求值和成绩落盘三个环节。4.1 输入清洗与答案归一化全角、空格、多余符号的处理在批改之前先做输入归一化。真实用户会用中文输入法打出全角数字和符号也会在答案两边敲空格。如果程序自己生成的题面用的是“12 3”用户输入“23”直接比较必然判错。public class AnswerNormalizer { public static String normalize(String raw) { if (raw null) { return ; } return raw.replaceAll([\\s\u00A0], ) .replace(, () .replace(, )) .replace(, ) .replace(, -) .replace(, *) .replace(, /) .replace(×, *) .replace(÷, /) .replace(,, ); } }这段代码把全角加减乘除和括号全部映射成半角同时去掉所有空白字符包括不换行空格。注意不要把小数点也给去掉因为除法或混合运算的结果可能是小数。字符映射顺序无所谓但务必要在“去除空格”之后执行否则“1 2”这种中间带空格的情况可能处理不干净。这里有个容易踩的坑题面里用“×”和“÷”批改端必须提前归一化。如果题面生成时用的是“×”用户照抄“×”而批改端只认识“*”那所有乘法都会误判。我习惯的做法是“题面生成用 × ÷批改归一化到 * /”两边各管各的但规则一致。4.2 表达式等价判定从equals到求值比较批改有两种严格程度第一种只认标准答案输入“15”就是对“105”就算错第二种允许等价表达式适用于应用题或更开放的题型。算术题练习应该用第一种因为目标是训练计算结果不是训练表达式变形。但无论哪种都不建议直接用 equals 比较用户输入和标准答案。更稳的做法是“把用户输入和标准答案都做一次求值再用 BigDecimal 比较”。这里不需要引入完整的表达式解析器因为题目只是一个二元运算。我常写一个最小求值函数public class AnswerMarker { public boolean check(String userInput, ArithmeticQuestion question) { String left AnswerNormalizer.normalize(userInput); String right AnswerNormalizer.normalize(question.getAnswer()); if (left.isEmpty()) { return false; } return compareValue(left, right); } private boolean compareValue(String left, String right) { try { BigDecimal leftValue evaluate(left); BigDecimal rightValue evaluate(right); if (leftValue null || rightValue null) { return false; } return leftValue.compareTo(rightValue) 0; } catch (NumberFormatException e) { return false; } } private BigDecimal evaluate(String expression) { String trimmed expression.trim(); // 如果用户直接输入答案 6则整体是一个数字 if (trimmed.matches(-?\\d(\\.\\d)?)) { return new BigDecimal(trimmed); } // 只处理 数字 运算符 数字 的简单情况 String[] parts trimmed.split((?[-*/])|(?[-*/])); if (parts.length ! 3) { return null; } BigDecimal leftValue new BigDecimal(parts[0].trim()); String operator parts[1].trim(); BigDecimal rightValue new BigDecimal(parts[2].trim()); switch (operator) { case : return leftValue.add(rightValue); case -: return leftValue.subtract(rightValue); case *: return leftValue.multiply(rightValue); case /: return leftValue.divide(rightValue, 12, RoundingMode.HALF_UP) .stripTrailingZeros(); default: return null; } } }参数说明compareTo 是 BigDecimal 的正确比较方式不能用 equals因为 new BigDecimal(2).equals(new BigDecimal(2.0)) 返回 false而 compareTo 返回 0 表示数值相等。除法统一保留 12 位小数做舍入避免循环小数导致比较失败。这段代码有一个前提题目本身是“数字 运算符 数字”的二元运算而且负数结果不会出现在表达式中。如果 generateor 允许负数被减数可能为负上面的 split 正则会把负号当成运算符导致解析错误。所以生成器和批改器的规则要配套当前章节的生成器默认 allowNegativefalse批改器就够用如果开放负数建议把批改器改成完整表达式求值。4.3 成绩、错题与练习过程的落盘CSV与UTF-8练习结束之后成绩和错题不能只留在弹窗里。常见做法是写入 CSV 文件方便用 Excel 打开也方便以后做历史统计。CSV 不需要额外依赖比 Properties 更适合表达结构化记录。private void saveRecord(File file, PracticeSession session) { try (BufferedWriter writer Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { StringBuilder line new StringBuilder(); line.append(LocalDateTime.now()).append(,); line.append(session.getCorrectCount()).append(,).append(session.getTotalCount()); for (ArithmeticQuestion question : session.getWrongList()) { line.append(,).append(question.getExpression()) .append().append(question.getAnswer()); } writer.write(line.toString()); writer.newLine(); } catch (IOException e) { // 实际项目中可以写到日志或弹窗提示 } }代码说明用 Files.newBufferedWriter 打开文件打开模式为 CREATE 加 APPEND这样每次练习追加一行不会覆盖历史记录。文件编码强制指定 UTF-8避免在中文 Windows 上出现乱码。时间戳用 java.time.LocalDateTime比老旧的 Date 更直观。CSV 的列结构是“时间, 答对数, 总题数, 错题1, 错题2, ...”。如果错题表达式里包含逗号列结构会被破坏。在本项目里题面只有数字和运算符不包含逗号所以可以简化处理将来如果加入文字题需要把包含逗号的字段用双引号包裹这是 CSV 转义的标准做法。读者如果只想做个最小版本可以不用 PracticeSession直接把成绩和错题拼成一行写文件。但项目交付后大概率会被要求“统计历史成绩”或“导出错题本”提前把保存逻辑独立出来后面改动要小得多。5. 避坑与排查算术题批改工具最容易翻车的5个现场这一章不是泛泛的“常见问题”而是我实际做这类工具时反复踩过的坑。每一条按“现象 → 原因 → 解决”展开排查顺序也基本适用。5.1 现象点了“提交”按钮毫无反应甚至整个窗口转圈原因分两种一是生成器在事件线程里做了大量耗时操作比如一次生成几百道题导致界面卡住二是事件监听器本身抛异常但控制台没打印看起来像是“没反应”。最典型的场景是把 1000 道题一次性生成放在按钮点击事件里Swing 的 EDT 被阻塞界面就假死了。解决方法是把生成操作放在构造阶段或启动时完成并且控制单次练习题量。一般一次 20 到 50 题足够。如果确实需要大量生成用 SwingWorker 在后台执行通过 publish 和 process 把题目一批批推给界面。还有一个隐藏点Swing 程序必须从事件分发线程启动main 方法里要写 SwingUtilities.invokeLater否则早期版本的组件初始化可能异常。5.2 现象练了50题题目频繁重复家长觉得不专业原因配置范围太小加法和乘法的随机排列有限。操作数范围 10 以内时加法题只有几十种不重复组合再叠加权重偏向重复率会很高。这不是随机数质量问题是样本空间太小。解决从两方面入手一是调大 maxOperand比如两位数加减法把范围放到 99二是在生成器内部加一个“近期去重”机制用一个固定容量的集合记录最近生成的题面生成新题时如果题面已存在就跳过重来但必须限制集合容量避免无限增长导致内存问题。private final SetString recentExpressions new LinkedHashSet(); private static final int RECENT_LIMIT 200; private boolean isTooRecent(String expression) { if (recentExpressions.contains(expression)) { return true; } recentExpressions.add(expression); if (recentExpressions.size() RECENT_LIMIT) { String oldest recentExpressions.iterator().next(); recentExpressions.remove(oldest); } return false; }这段代码用 LinkedHashSet 保持插入顺序超出容量时移除最老的一条。它的逻辑是“不无限保留历史只保证近期不重复”运用于口算练习比全量去重更合适。5.3 现象标准答案是“6 ÷ 3 2”用户输入“2.0”被判错原因八成是用了 String.equals 或者 double 比较。double 的 0.10.2 不等于 0.3 这种精度问题大家多少知道但 String.equals 的坑更隐蔽题面生成器输出的是“2”用户手输“2.0”肉眼看起来一样程序就是不相等。解决办法就是把批改统一改成数值比较。上章给出的 AnswerMarker 已经用 BigDecimal.compareTo 处理了这个问题。这里再强调一个细节不要用 BigDecimal 的 equals它会把精度也算进相等条件compareTo 只看数值大小。标准答案和用户输入在规范化后都先做一次求值然后比较。5.4 现象代码在自己电脑上正常打包到别的机器中文全变问号原因源码文件编码和运行时读取编码不一致。早期 JDK 在编译时默认使用平台编码中文 Windows 下是 GBK如果你用 UTF-8 保存源码又没有在编译参数里指定 UTF-8编译出来的 class 里中文字符就乱了。运行时如果 JVM 默认编码和终端编码不一致控制台或弹窗里的中文也可能乱。解决方法是统一编码源码文件统一用 UTF-8 保存编译时显式指定编码如果有构建脚本写成下面这样javac -encoding UTF-8 -d out src/practice/*.java在 IntelliJ 或 Eclipse 里把项目默认编码设置为 UTF-8同时改运行环境的 VM 参数为 -Dfile.encodingUTF-8三个地方对齐才能保证换机后不乱码。注意如果使用者是老旧 JDK 8 环境这个参数仍然有效。5.5 现象双击 jar 包没有任何窗口但命令行能运行原因可执行 jar 的 META-INF/MANIFEST.MF 里没有声明 Main-Class或者声明的类名和实际包名不一致。双击 jar 时系统调用 javaw.exe它把启动异常吞掉了所以什么提示都没有。排查顺序一定是先回命令行java -jar practice.jar如果提示 “no main manifest attribute”就用压缩工具打开 jar 查看 MANIFEST.MF确认有没有 Main-Class 行并且类名必须是全限定名例如Main-Class: com.example.practice.PracticeApp更稳妥的做法是把 jar 包的构建写进脚本或 IDE 配置而不是手动打 jar。这里还要注意jar 内部类的包名路径必须和源码目录结构一致否则明明写了 Main-Class 也报 ClassNotFoundException。把一个可运行 jar 交给使用者后最省心的方式是附带一个 run.bat里面用 javaw 启动并把控制台错误重定向到日志文件这样使用者反馈问题时你至少能拿到报错。6. 从源码到.rar交付可复现的构建与三个值得做的扩展6.1 交付.rar而不是单独jar的原因标题里出现“.rar”说明交付物不只是程序而是“源码 可执行包 使用说明”的组合包。单独给一个 jar 对使用者不够友好单独给源码对方还要自己配环境。常见做法是整理一个目录结构再压缩成 .rar 或 .zip目录或文件作用src/全部 Java 源码便于阅读和二次开发dist/practice.jar可直接运行的可执行 jarREADME.txt运行环境、启动方式、操作说明history/练习记录目录演示批改数据落盘README 里至少写三句需要哪个版本 JDK、双击 jar 还是命令行运行、题目参数在哪里改。很多交付后的问题都来自“使用者不知道怎么看报错”所以建议在 README 中写明“遇到问题先在命令行执行 java -jar dist/practice.jar”。6.2 值得继续做的扩展与一个自检习惯第一个扩展是错题重做。PracticeSession 已经收集了 wrongList结束弹窗里加一个“重新练习错题”按钮把 wrongList 传入新的会话即可。第二个扩展是生成整张练习卷并导出文本方便老师打印用 StringBuilder 拼 50 道题按每行两道写入 txt。第三个扩展是单元测试自动化验证生成器边界比如固定种子生成 1000 道题断言没有负数、所有除法都能整除、答案范围合法。最后说一个我的习惯每次改完生成器或批改器都在 main 方法里用固定种子跑一遍批量自检再把 window 界面跑一次。这比随机点几道题可靠得多。以前我在批改里偷懒用 equals后来收到“2.0 判错”的反馈才换上 BigDecimal 求值。从那以后凡是涉及结果比较的代码我都会先问一句“用户输入会不会和标准答案在格式上有差异”。如果你也按这个顺序把生成、界面、批改、打包做一遍交付出去就不容易返工。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

1. 这个布局到底解决了什么痛点做过Android复杂详情页或者多列表嵌套的兄弟应该都有过这种体验:一个页面里塞了Banner轮播、横向商品列表、纵向评论列表、底部推荐流,每个模块单独拿出来滑动都没问题,可一旦拼在同一个页面里,手指…

2026/10/14 16:22:34 阅读更多 →
八皇后问题的工程化实现:DFS剪枝与Java性能优化

八皇后问题的工程化实现:DFS剪枝与Java性能优化

1. 项目概述:一道经典题目的真实分量“八皇后”这三个字,在算法入门圈子里,几乎等同于“递归初体验”“回溯第一课”“DFS手把手教学”。但很多人刷完洛谷P1219后只记得“输出前三个解总解数”,却没意识到——这道标着“java&…

2026/10/14 16:22:34 阅读更多 →
Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

区块链Web3 【免费下载链接】grin Minimal implementation of the Mimblewimble protocol. 项目地址: https://gitcode.com/gh_mirrors/gr/grin 点击查看 免费下载 本篇技术指南以 Grin 仓库中的《Blocks and Block headers》文档为骨架,系统讲解 Mimbl…

2026/10/14 16:22:34 阅读更多 →

最新新闻

学术AI工具怎么选更划算?六款科研工具按环节搭配的性价比盘点

学术AI工具怎么选更划算?六款科研工具按环节搭配的性价比盘点

一、工具越多,性价比这笔账越算越乱1、行业背景。 近几年大模型能力快速落地,学术场景成了试验田。检索、翻译、改写、润色、绘图,几乎每个环节都冒出专门工具,订阅方式从免费额度到按年付费各有不同。选择变多了,判断成本也跟着涨,同一笔预算怎么分配成了新问题。2、不同人群的…

2026/10/14 17:01:19 阅读更多 →
仓颉语言服务 LSPServer 使用指南:启动参数、运行环境与源码级原理解析

仓颉语言服务 LSPServer 使用指南:启动参数、运行环境与源码级原理解析

开发工具CLI代码质量 【免费下载链接】cangjie_tools 仓颉编程语言命令行工具,包括仓颉包管理工具、仓颉格式化工具、仓颉多语言桥接工具及仓颉语言服务。 项目地址: https://gitcode.com/Cangjie/cangjie_tools 点击查看 免费下载 本文以 cangjie-lang…

2026/10/14 17:01:19 阅读更多 →
openJiuwen MemoryCrypto 解析:记忆引擎 AES-256-GCM 加解密实战指南

openJiuwen MemoryCrypto 解析:记忆引擎 AES-256-GCM 加解密实战指南

人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习 【免费下载链接】agent-core-java openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。 项目地址: https://gitco…

2026/10/14 17:01:19 阅读更多 →
树莓派还是Arduino?跑系统还是跑裸机?从真实项目谈选型

树莓派还是Arduino?跑系统还是跑裸机?从真实项目谈选型

很多刚开始搞硬件开发的朋友,总会拿着一块树莓派和一块 Arduino 问我:“到底买哪个?”几乎所有社区里都会有人甩出一句经典老话:“一个跑系统,一个跑裸机。”听起来很合理,选型好像一下子就清楚了。但这几年…

2026/10/14 17:01:19 阅读更多 →
手动搭建MySQL MHA高可用架构:从主库宕机到自动切换实战

手动搭建MySQL MHA高可用架构:从主库宕机到自动切换实战

1. 为什么手动搭建MHA依然值得学1.1 从一次主库宕机说起凌晨两点,监控告警响了,主库连接数直接掉零。当时我们那套业务用的是单点MySQL,没有做任何高可用。运维同学从被窝里爬起来,手动切从库、改配置、重启服务,前后折…

2026/10/14 17:01:19 阅读更多 →
Oracle 19c RAC on Linux 7.6 安装实战:从环境检查到静默建库

Oracle 19c RAC on Linux 7.6 安装实战:从环境检查到静默建库

简介:面向需要在Red Hat Enterprise Linux 7.6上部署Oracle 19c RAC的DBA与运维工程师,这份安装手册将环境准备与集群配置拆分为可逐步跟做的阶段,涵盖OS环境检查、关闭THP并开启Hugepages、依赖包与内核参数设置、时钟同步等前置任务&#x…

2026/10/14 17:00:18 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →