题目集1~3总结性Blog
一.前言
1.题目集1
(1)知识点:
7-1 设计一个风扇Fan类:
类的设计:有参构造方法 ,无参构造方法,toString()的方法返回描述对象的字符串,成员变量(数据域)的访问器和修改器的设计。
7-2 类和对象的使用:
类和对象的使用,构造方法的重载。
7-3 成绩计算-1:
类、数组的基本运用。
7-4 成绩计算-2:
关联类,关联关系的实现。
7-5 答题判题程序-1:
- 类的设计与封装
- 代码包含三个主要类:
Title、TestPaper和AnswerSheet,分别表示题目、试卷和答题纸的概念。 - 每个类封装了特定的数据和方法,实现了类的职责分离,便于管理和维护:
Title:封装了题目的 ID、内容和标准答案,并提供了checkAnswer方法用于答案验证。TestPaper:存储Title对象,并提供方法添加题目、按 ID 获取题目、检查题目答案等。AnswerSheet:存储用户答案和评判结果,关联一个TestPaper对象,并实现了答案存储和评判结果输出的方法。
- 集合与泛型
- 代码使用了
Map和List两种常用集合来存储数据。 TestPaper类中的titles使用TreeMap,根据titleID的顺序存储Title对象,便于按题号排序。AnswerSheet类中的answers和Judgeresults使用了List,以顺序存储用户答案和评判结果。- 泛型的使用:
TestPaper类的Map<Integer, Title>指定了TreeMap的键和值的类型,确保了键为Integer,值为Title。 - 注意:
AnswerSheet类中的answers和Judgeresults应使用泛型声明(如List<String>和List<Boolean>),以提高类型安全性。
- 正则表达式的使用
- 正则表达式
Pattern和Matcher被用于从输入数据中提取题目编号、问题内容和标准答案,格式为#N: <题号> #Q: <问题> #A: <答案>。 - 这部分代码利用了捕获组匹配来解析题目的结构,使代码更简洁、易于维护。
- 输入与数据处理
- 输入的处理采用
Scanner从控制台逐行读取用户输入。Pattern用于解析题目输入,并在Main方法中将解析的题目添加到TestPaper。 - 在处理用户答案时,代码使用
split方法去除#A:前缀,并存储在AnswerSheet类的answers列表中。
- 关联关系
TestPaper和Title:一种聚合关系,TestPaper由多个Title组成,并将Title存储在Map中。AnswerSheet和TestPaper:一种关联关系,AnswerSheet类依赖于TestPaper类来检查答案的正确性。
- 面向对象编程原则
- 单一职责原则:每个类专注于单一功能,便于代码的解耦和维护。
- 封装:
Title、TestPaper、AnswerSheet类都对内部数据进行了封装,仅通过公共方法访问。 - 高内聚低耦合:各类之间依赖关系清晰,降低了耦合度,使各类可以独立工作。
- 逻辑控制与条件判断
Main方法中有多个条件判断,用于处理数据输入的不同阶段。AnswerSheet类中的 printAnswersAndJudgeResults()方法,通过条件判断决定结果输出格式(如最后一组结果不添加空格)。
- 边界与错误处理
checkAnswer方法判断用户答案是否匹配标准答案,避免了因大小写或空格导致的错误。- 代码没有对输入的格式进行严格检查,可以增加对输入格式的边界检查和错误处理,确保输入数据的有效性。
9.字符串处理:
- 对用户输入进行清理和处理,例如使用 trim() 方法去除多余的空格,以及通过 replace() 方法去掉特定的前缀。
(2)题量、难度
前面几题题量小,难度小
最后一题题量较小,但是难度较大
2.题目集2
(1)知识点:
7-1 手机按价格排序、查找:
- 接口实现与比较操作
MobilePhone类实现了Comparable<MobilePhone>接口,重写了compareTo方法用于定义排序规则:compareTo方法中使用Integer.compare(this.price, other.price)实现了价格的升序排序。- 实现
Comparable接口后,可以通过Collections.sort()对MobilePhone对象进行自然排序(根据价格升序)。 - 这是 Java 中定义排序规则的常用方式之一,使得对象可以直接参与排序操作。
2.ArrayList 的使用
ArrayList用于存储MobilePhone对象。ArrayList是一种动态数组,支持自动扩容,便于管理一组对象。- 代码通过
ArrayList存储了三个MobilePhone对象,并通过Collections.sort()对ArrayList中的对象排序。
- **
Collections.sort()方法
Collections.sort(mobilephoneList)使用了集合工具类Collections提供的排序方法,可以对实现了Comparable接口的对象进行自然排序。- 由于
MobilePhone实现了Comparable接口并定义了价格排序规则,Collections.sort()可以直接对ArrayList<MobilePhone>排序。
7-2 sdut-oop-4-求圆的面积:
- 访问控制和封装
radius属性被声明为私有 (private),只能通过setRadius和getRadius方法进行访问和修改。这种封装方式保证了radius的值只能通过类中定义的接口进行操作,避免了直接访问带来的不安全性。- 使用
public的getRadius和setRadius方法,外部可以安全地访问和更新radius的值。同时在setRadius方法中对输入进行了验证,确保了radius的值始终是一个有效的正数。
- 面向对象的封装和数据验证
- 在
setRadius和带参构造方法中,都加入了radius值的有效性验证,以确保radius始终为正数。 - 使用这种数据验证机制确保对象的状态符合业务逻辑需求,避免因为无效数据导致的不正确计算或错误输出。
- 库函数的调用
getArea方法用于计算并返回圆的面积,计算公式为Math.PI * Math.pow(this.radius,2),使用了Math库中PI和pow方法。- 使用单独的方法来计算面积可以提高代码的模块化,并使得调用者无需关心面积的计算逻辑,直接通过该方法得到结果。
- 控制台输入和对象属性修改
- 在
Main类中,通过Scanner对象从控制台读取半径值,用c2.setRadius(r1)动态修改c2对象的radius值。 - 这种交互方式模拟了用户动态输入的情境,展现了如何通过控制台输入与对象方法交互,实时更新对象的属性。
- 格式化输出
- 使用
System.out.printf("%.2f%n", c1.getArea())格式化输出面积,保留两位小数。%.2f控制浮点数的输出格式,.2表示保留两位小数,%n表示换行。 - 格式化输出提升了输出的美观性,尤其是在需要控制精度或格式的场合。
7-3 Java类与对象-汽车类
代码的补充,解读
7-4 答题判题程序-2
- 类的设计与面向对象编程
Title类:代表考试中的一道题目,包含题目ID、内容、标准答案,以及用于验证用户答案的checkAnswer方法。TestPaper类:表示一张试卷,包含题目列表及其对应分数,提供方法来添加题目、获取题目、获取题目分数及计算试卷总分。AnswerSheet类:表示一份答题卡,存储用户的答案列表,使用judgeAnswers方法来自动判分。printAnswersAndJudgeResults和printScores分别用于输出答题和分数结果。
- 正则表达式
- 正则表达式用于解析输入数据,使程序能动态读取并提取题目内容和答卷数据:
titlePattern:匹配题目定义行,例如#N: titleID #Q: question #A: standardAnswer。testPattern:匹配试卷定义行,例如#T: testID titleID-score。answerPattern:匹配答题行,例如#S: testID #A: answer。
- 数据结构 (集合类和Map)
- 使用
Map组织试卷和题目,便于快速根据题目ID查找题目信息。 - 使用
List存储答案和判分结果,在AnswerSheet类中对用户答案进行逐题判断。
- 自动判分与条件判断
- 答案校验:
AnswerSheet类中的judgeAnswers方法对用户答案进行逐题判分,判断对错并累加总分。 - 分数检查:在试卷定义时立即检查总分是否为100分,并输出相应警告信息。
- 代码逻辑的简化与优化
- 代码采用
LinkedHashMap来保证题目顺序,List存储判分结果,Map映射题目ID与分数,避免了嵌套循环,代码简洁易读。
(2)题量、难度
前面几题题量小,难度小
最后一题题量较大,难度较大
3.题目集3
(1)知识点
7-1 面向对象编程(封装性):
类的封装。
7-2 jmu-java-日期类的基本使用:
- Java时间API (
java.time)
LocalDate类:代表日期对象,且只包含年、月、日,不包含时间。ChronoUnit枚举:用于计算日期间隔,提供了DAYS、MONTHS、YEARS等多种常量,可以简化日期操作。
- 日期解析与异常处理
LocalDate.parse(dateStr):将字符串解析为LocalDate对象,要求输入符合“yyyy-MM-dd”格式。- 异常处理:
parseDate方法捕获DateTimeException异常,以便处理非法日期并返回null,这样可以优雅地应对用户输入的错误格式或无效日期。
- 闰年判断
isLeapYear方法:调用LocalDate的isLeapYear方法判断日期所在年份是否为闰年。使用闰年判断可以识别2月是否有29天。
- 日期差计算
- 天数差:使用
ChronoUnit.DAYS.between(start, end)计算天数间隔,适用于直接计算天数。 - 年数和月数差:通过日期对象的
getYear和getMonthValue计算年数差和月数差,以获取大概的年份和月份间隔。 - 由于年数和月数间隔的计算并未考虑日期的精确性(如考虑“2024-01-01”到“2025-12-01”之间的实际月份差),年数和月数差计算方法可视作一种基础近似。
- 日期信息的提取
- 使用
LocalDate的方法提取以下日期信息: getDayOfYear():获取日期在一年中的位置。getDayOfMonth():获取日期在当月的位置。getDayOfWeek():获取日期是星期几。
7-3 答题判题程序-3:
- 面向对象设计
Title类:封装了题目信息,包括题号、内容、标准答案、删除标记及答案校验方法。TestPaper类:封装了试卷信息,包括题目排序与分数、总分计算等功能。AnswerSheet类:用于封装答题信息,包含用户答案与判分结果等。Main类:作为主程序控制流,负责处理输入、管理题库与试卷、生成答卷并输出结果。
- 集合和映射的使用
Map映射:Map在多处用于将题目编号、题目内容、试卷与学生答案等数据关联,如orderToTitleID(题目顺序到ID的映射)、titles(试卷题目集合)等。Set集合:Set用于存储不可用题目ID,便于检测无效题目。List集合:在判分时用于存储每道题的判断结果(对错),并在打印判分结果时循环取用。
- 正则表达式解析
- 正则表达式解析输入:通过多组正则表达式(如
titlePattern、testPattern等)解析不同格式的输入行,用以抽取题目信息、试卷分数、学生信息等。 - 格式检查与提示:未匹配的行通过
else条件抛出“格式错误”提示,确保用户输入符合规范。
- 异常处理
- 格式错误检测:在用户输入答案时,通过判断输入格式是否符合预期,若不符则抛出
IllegalArgumentException并提示“格式错误”。 - 空输入跳过:跳过空白或格式不符的答案字符串,以防止程序因无效数据而崩溃。
- 数据封装与层次化设计
- 代码将不同功能封装在不同类中,并通过
Main类协调各个模块,保证了代码的可读性、扩展性和易维护性。例如,AnswerSheet封装了答题判分逻辑,而TestPaper则负责试卷信息和分数管理,使得各类之间职责明确。
(2)题量、难度
前面几题题量小,难度小
最后一题题量较大,难度较大
二.设计与分析
各题目集前面几题都相对简单,我们这里重点分析最后一题。
7-5 答题判题程序-1:
类图:

顺序图:

程序流程:
以下是这个程序的流程和主要函数调用关系:
程序流程概述
- 主程序入口 (
Main.main)
- 程序从
main方法开始,通过Scanner从输入读取题目数量,并逐一输入每个题目的详细信息。 - 每个题目信息以特定格式提供 (
#N:<题号> #Q:<题目内容> #A:<标准答案>),程序使用正则表达式进行解析。 - 将每个解析出的题目生成
Title对象,存储到TestPaper中。 - 然后,程序读取用户答案,提取每个答案,并将其传递给
AnswerSheet进行处理。
- 题目和答案处理流程
- 将所有用户答案存储并在
AnswerSheet中调用saveAnswers方法进行判定。 saveAnswers方法遍历用户答案,将每个答案与TestPaper中的标准答案对比,判断对错并存储结果。
- 输出判定结果
- 通过
AnswerSheet.printAnswersAndJudgeResults方法,依次输出每个题目内容及用户答案,然后输出每个判定结果(对错)。
关键类与方法
- 类
Title
- 表示单个题目及其标准答案。
- 主要方法:
checkAnswer:对比用户答案是否与标准答案一致(忽略大小写和空格)。
- 类
TestPaper
- 管理多个
Title对象的集合,以Map存储并支持按题号排序。 - 主要方法:
addTitle:向titles中添加一个题目。getTitleByID:根据题号获取特定题目。checkAnswer:调用Title.checkAnswer检查特定题目的答案。
- 类
AnswerSheet
- 表示用户提交的答案,与标准答案对比并保存对错结果。
- 主要方法:
saveAnswers:接受用户答案列表,清理无效字符并检查每个答案是否正确,将结果存储到Judgeresults中。printAnswersAndJudgeResults:输出题目内容、用户答案及判断结果。
- 类
Main
- 程序入口,负责读取输入、初始化
TestPaper和AnswerSheet,并调用其方法完成操作。 - 主要流程:
Pattern正则表达式解析输入,将题目和答案数据提取并存储。- 检查输入的用户答案格式,调用
AnswerSheet.saveAnswers保存并判定答案正确性。 - 最后,调用
AnswerSheet.printAnswersAndJudgeResults输出所有结果。
函数调用关系
Main.main
├── TestPaper.addTitle (创建并添加题目到 TestPaper)
│ └── Title (创建 Title 对象)
│
├── AnswerSheet.saveAnswers (保存和判断答案)
│ ├── TestPaper.checkAnswer (在 TestPaper 中检查答案)
│ │ └── Title.checkAnswer (调用 Title 判断答案是否正确)
│ └── 结果保存至 Judgeresults
│
└── AnswerSheet.printAnswersAndJudgeResults (输出题目及判定结果)
重点代码分析:
1. checkAnswer 方法
public boolean checkAnswer(String answer) {
return standardAnswer.equalsIgnoreCase(answer.trim());
}
- 功能:将
answer首尾空格去掉后(trim()),与standardAnswer进行忽略大小写的比较。 - 参数:
answer是用户输入的答案字符串。 - 返回值:若用户答案和标准答案一致(忽略大小写和首尾空格差异),返回
true;否则返回false。
2. addTitle方法
public void addTitle(Title title) {
titles.put(title.getTitleID(), title);
}
- 功能:将一个
Title对象添加到titles集合中。 - 参数:
title是一个Title对象,代表一个题目。 - 细节:使用
title.getTitleID()作为键,将title存入Map集合titles中,确保题目通过其唯一 ID 来索引。
3. getTitleByID方法
public Title getTitleByID(int titleID) {
return titles.get(titleID);
}
- 功能:根据题目 ID 获取对应的
Title对象。 - 参数:
titleID是题目的唯一标识符。 - 返回值:如果找到匹配的题目 ID,返回对应的
Title对象;否则返回null。
4. checkAnswer方法
public boolean checkAnswer(int titleID, String answer) {
Title title = getTitleByID(titleID);
return title != null && title.checkAnswer(answer);
}
- 功能:检查用户对特定题目的答案是否正确。
- 参数:
titleID为题目 ID,answer为用户提供的答案。 - 返回值:若题目 ID 存在且答案正确,返回
true;否则返回false。 - 细节:调用
getTitleByID获取题目对象,然后调用该题目的checkAnswer方法判断答案是否正确。
5. getSortedTitles方法
public Map<Integer, Title> getSortedTitles() {
return titles;
}
- 功能:返回包含所有题目的
Map集合。 - 返回值:按题目 ID 排序的
Map<Integer, Title>集合,因为titles是一个TreeMap(自动按键排序)。
6. saveAnswers 方法
public void saveAnswers(String[] UserAnswers) {
for (int i = 0; i < UserAnswers.length; i++) {
int titleID = i + 1;
String cleanedAnswer = UserAnswers[i].replace("#A:", "").trim(); // 去除 #A: 前缀并去掉空格
answers.add(cleanedAnswer);
boolean IsCorrect = testPaper.checkAnswer(titleID, cleanedAnswer);
Judgeresults.add(IsCorrect);
}
}
- 功能:从
UserAnswers数组中提取每个用户答案,清理掉无关字符并与标准答案对比,存储判定结果。 - 步骤:
- 遍历用户答案数组
UserAnswers。 - 计算题目 ID(假设答案顺序与题目 ID 对应):
题目 ID =i + 1,假设UserAnswers[0]是第 1 题的答案,以此类推。 - 清理用户答案:去掉前缀
#A:,并去掉首尾空格。 - 将清理后的答案添加到
answers列表中。 - 调用
testPaper.checkAnswer检查答案正确性,将结果存入Judgeresults列表。
- 遍历用户答案数组
7. printAnswersAndJudgeResults 方法
public void printAnswersAndJudgeResults() {
Map<Integer, Title> sortedTitles = testPaper.getSortedTitles();
for (Integer titleID : sortedTitles.keySet()) {
Title title = sortedTitles.get(titleID);
if (title != null) {
System.out.println(title.getContent() + "~" + answers.get(titleID - 1));
}
}
for (int i = 0; i < Judgeresults.size(); i++) {
System.out.print(Judgeresults.get(i));
if (i < Judgeresults.size() - 1) {
System.out.print(" ");
}
}
System.out.println();
}
- 功能:输出每道题目的内容和用户答案,并打印每个答案的判定结果(
true表示正确,false表示错误)。 - 步骤:
- 获取题目集合
sortedTitles。 - 遍历
sortedTitles的每个题目 ID:
获取对应题目内容和用户的答案。
格式化输出为题目内容~用户答案。 - 遍历
Judgeresults列表,输出判定结果true或false,中间用空格分隔。
- 获取题目集合
7-4 答题判题程序-2:
类图:

顺序图:

程序流程:
程序整体流程
- 输入解析和存储:
- 程序通过
Scanner接收多行输入并存储在inputLines列表中,直到遇到"end"。 - 分析输入行是否匹配题目、试卷或答卷的格式,并将解析结果存储在相应的集合中。
- 题目、试卷和答案的解析:
- 逐行解析
inputLines,判断每行是否是题目、试卷或答卷。 - 将解析得到的题目信息存储在
allTitles中,试卷信息存储在testPapers中,答卷则用于创建AnswerSheet。
- 试卷验证和输出:
- 在添加每张试卷时,检查其总分是否等于100分,若不等于100分,输出警告信息。
- 每次创建
AnswerSheet对象时,调用其方法输出答题和判分结果。
核心函数调用关系
- Main 类:
- 主要负责处理输入,并根据输入构建
Title、TestPaper和AnswerSheet对象。 - 调用顺序:
- 调用
Title构造函数创建题目。 - 调用
TestPaper.addTitle()为试卷添加题目和分数。 - 调用
AnswerSheet构造函数解析用户**,并输出答题和判分结果。
- Title 类:
Title对象保存题目内容和标准答案。checkAnswer:检查用户答案是否与标准答案匹配,忽略大小写和空格。
- TestPaper 类:
- 存储试卷的题目、每道题的分数,并支持计算试卷总分。
addTitle:将Title对象和对应的分数添加到试卷中。getScoreByTitleID:返回特定题目的分数。getTotalScore:计算并返回试卷的总分。
- AnswerSheet 类:
- 保存用户的答案,判定其正确性,并根据答对的题目计算得分。
- 主要方法:
judgeAnswers:判定答案是否正确,更新判定结果和总得分。printAnswersAndJudgeResults:按顺序打印题目、用户答案和判定结果。printScores:打印每题得分和总分。
函数调用关系
+----------------------------------+
| Main 类 |
+----------------------------------+
| |
| 读取输入,存入 inputLines |
| 遍历 inputLines,根据类型解析 |
| |
| 如果是题目: |
| - 解析题目,创建 Title 对象 |
| - 存储到 allTitles |
| |
| 如果是试卷: |
| - 解析试卷ID和题目分数 |
| - 创建 TestPaper 对象 |
| - 为 TestPaper 添加 Title |
| - 检查 TestPaper 总分是否为100 |
| |
| 如果是答卷: |
| - 解析答卷ID和用户答案 |
| - 检查 TestPaper 是否存在 |
| - 创建 AnswerSheet 对象 |
| - 调用 AnswerSheet 方法打印结果 |
+----------------------------------+
|
v
+----------------------------------+
| Title 类 |
+----------------------------------+
| 属性: titleID, content, |
| standardAnswer |
| 方法: |
| checkAnswer() |
+----------------------------------+
|
v
+----------------------------------+
| TestPaper 类 |
+----------------------------------+
| 属性: testPaperID, titles, |
| scores |
| 方法: |
| addTitle() |
| getScoreByTitleID() |
| getTotalScore() |
| getTitles() |
+----------------------------------+
|
v
+----------------------------------+
| AnswerSheet 类 |
+----------------------------------+
| 属性: testPaper, answers, |
| judgeresults, totalScore |
| 方法: |
| judgeAnswers() |
| printAnswersAndJudgeResults() |
| printScores() |
+----------------------------------+
重点代码分析:
-
checkAnswer方法:

checkAnswer方法用于判断用户输入的答案answer是否正确。它将用户答案answer和标准答案standardAnswer进行比较,忽略大小写和多余空格。如果相同,则返回true。 -
addTitle方法:

addTitle方法用于添加问题和分数。每个问题由Title对象表示,它的titleID作为键存入titles,而score则存入scores。 -
getTotalScore方法:

getTotalScore方法计算并返回测试的总分。它遍历scores集合中的每个分数,累加求和并返回总分。 -
judgeAnswers方法:

judgeAnswers方法用于对每个用户答案进行判断并记录结果。
- 首先获取测试题目集合
titles。 - 逐一检查每个问题的答案,如果存在对应答案,则通过
checkAnswer方法检查正确性,将结果存入judgeresults。 - 如果答案正确,将对应题目的分数添加到总分
totalScore。 - 如果用户没有提供答案,则将
false记录到judgeresults。
printAnswersAndJudgeResults方法:
public void printAnswersAndJudgeResults() {
Map<Integer, Title> sortedTitles = testPaper.getTitles();
int i = 0;
for (Integer titleID : sortedTitles.keySet()) {
Title title = sortedTitles.get(titleID);
String answer = i < answers.size() ? answers.get(i).trim() : "answer is null";
if (answer.equals("answer is null")) {
System.out.println("answer is null");
} else {
boolean isCorrect = judgeresults.get(i);
System.out.println(title.getContent() + "~" + answer + "~" + isCorrect);
}
i++;
}
}
printAnswersAndJudgeResults 方法用于打印用户答案及其正确性。
- 遍历题目集合并获取每个题目的答案,如果没有提供答案,则输出
"answer is null"。 - 否则,显示问题内容、答案和判定结果
isCorrect,用~符号分隔。
printScores方法:
public void printScores() {
int i = 0;
Map<Integer, Integer> scores = new LinkedHashMap<>();
for (Integer titleID : testPaper.getTitles().keySet()) {
if (i < answers.size() && judgeresults.get(i)) {
scores.put(titleID, testPaper.getScoreByTitleID(titleID));
} else {
scores.put(titleID, 0);
}
i++;
}
int j = 0;
int total = 0;
int numScores = scores.size(); // 获取总的得分数量
for (Integer score : scores.values()) {
System.out.print(score); // 直接打印分数,不添加空格
total += score;
if (j < numScores - 1) { // 如果不是最后一个分数,添加空格
System.out.print(" ");
}
j++;
}
System.out.println("~" + total); // 打印总分,格式为 ~total
}
printScores 方法用于打印每个问题的得分以及总分。
- 通过遍历题目集合,检查答案是否正确,如果正确,将对应题目的分数添加到
scores,否则计为0。 - 最后,将每个问题的得分按顺序打印,以空格分隔,并在最后添加总分
7-3 答题判题程序-3:
类图:

顺序图:


程序流程:
程序整体流程
- 程序入口 (
main方法)
- 初始化数据结构:
testPapers、allTitles、students等Map,用于存储试卷、题目和学生信息。 - 定义正则表达式:分别用于匹配输入中的题目、试卷、答案、删除和学生信息格式。
- 读取输入:将所有输入行收集到
inputLines列表中,并在读取结束时停止输入(即遇到"end"行)。
- 解析输入
遍历inputLines中的每一行,根据不同类型信息调用对应的解析处理:
-
题目信息解析(使用
titlePattern):
通过Title构造方法创建Title对象,存储到allTitles。 -
调用关系:
new Title(...) -> allTitles.put(...) -
试卷信息解析(使用
testPattern):
创建TestPaper对象,并将题目和分数添加到试卷。 -
调用关系:
new TestPaper(...) -> testPaper.addTitle(...) -> testPapers.put(...) -
分数检查:调用
getTotalScore验证试卷总分是否为100分,不满足时输出警告。 -
删除题目信息解析(使用
deletePattern):
通过题目ID找到Title对象,并调用delete()标记其为已删除。 -
调用关系:
allTitles.get(...).delete() -
学生信息解析(使用
studentPattern):
将学生ID和姓名保存到studentsMap。 -
调用关系:
students.put(...) -
格式错误检查:
检查输入行是否符合格式,不符合则输出"wrong format: " + line。
- 处理答卷
- 遍历
inputLines再次匹配answerPattern,获取答卷的测试ID、学生ID、学生答案。 - 检查学生ID和试卷ID是否存在,若缺少则输出警告信息。
- 若存在有效的测试ID,执行以下操作:
- 评分流程
-
创建答题卡:根据学生答案数组
userAnswersWithOrder创建AnswerSheet对象。 -
调用关系:
new AnswerSheet(...) -
构造函数调用链:解析并存储答案后,调用
judgeAnswers()方法对答案进行判断。 -
judgeAnswers()方法:
遍历testPaper中的所有题目,检查每道题的答案是否正确,记录判定结果和总分: -
调用关系:
Title.checkAnswer(...) -> testPaper.getScoreByTitleID(...)
- 输出结果
-
显示判定结果:调用
printAnswersAndJudgeResults()输出每道题的内容、用户答案及正确性。 -
调用关系:遍历
sortedTitles并根据nonExistentQuestions和isDeleted状态处理各种答案输出。 -
打印分数:调用
printScores显示学生每道题的得分和总分。 -
调用关系:遍历
scores并累加打印。
函数调用关系总结
main调用Title、TestPaper和AnswerSheet构造函数以存储题目、试卷和答题卡信息。AnswerSheet构造函数 完成答案解析后自动调用judgeAnswers评分。- 评分完成后,通过
printAnswersAndJudgeResults和printScores分别输出判定和得分信息。
总流程图
main()
│
├──> parse inputLines (title, test, delete, student info, answers)
│
├──> create TestPaper and Title objects
│
├──> create AnswerSheet for each answer -> judgeAnswers()
│ ├──> Title.checkAnswer() and score accumulation
│
├──> printAnswersAndJudgeResults()
│
└──> printScores()
整个程序流程主要是依次解析输入、构造对象、执行评分判断、并输出结果。
重点代码分析:
- 构造函数
AnswerSheet
public AnswerSheet(TestPaper testPaper, String[] userAnswers, Set<Integer> nonExistentQuestions) {
this.testPaper = testPaper;
this.nonExistentQuestions = nonExistentQuestions;
// 解析答案,并在错误格式时提示
try {
for (String ans : userAnswers) {
if (ans.trim().isEmpty()) {
continue; // 如果答案为空,则跳过
}
String[] parts = ans.replaceFirst("^#A:", "").split("-");
if (parts.length != 2)
throw new IllegalArgumentException("wrong format: " + ans);
int questionOrder = Integer.parseInt(parts[0].trim());
String answerContent = parts[1].trim();
answers.put(questionOrder, answerContent); // 标注问题顺序到答案
}
judgeAnswers();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
- 参数:
testPaper(对应的试卷对象)、userAnswers(学生提交的答案数组)、nonExistentQuestions(不存在的题目集合)。 - 功能:解析学生答案并存储到
answersMap 中,key为题目顺序,value为答案内容。 - 异常处理:若格式不符合
#A:题号-答案,则抛出异常提示格式错误。 - 评分:解析完成后调用
judgeAnswers方法判定各题答案是否正确。
- 方法
judgeAnswers
private void judgeAnswers() {
Map<Integer, Title> titles = testPaper.getTitles();
for (Integer order : titles.keySet()) {
Title title = titles.get(order);
String answer = answers.get(order);
if (answer == null || answer.trim().equals("")) {
judgeresults.add(false);
continue;
}
if (title == null || title.isDeleted()) {
judgeresults.add(false);
} else {
boolean isCorrect = title.checkAnswer(answer);
judgeresults.add(isCorrect);
if (isCorrect) {
totalScore += testPaper.getScoreByTitleID(order);
}
}
}
}
- 功能:遍历试卷中的所有题目,逐题判断答案的正确性。
- 流程:
- 取得试卷中每题的标题对象
Title和学生提交的对应答案answer。 - 若答案为空,或题目已被删除,则标记为
false(答案错误),跳过该题。 - 若答案非空且题目有效,调用
checkAnswer判断答案是否正确,结果加入judgeresults列表。 - 若答案正确,累加该题分数到
totalScore。
- 取得试卷中每题的标题对象
- 方法
printAnswersAndJudgeResults
public void printAnswersAndJudgeResults() {
Map<Integer, Title> sortedTitles = testPaper.getTitles();
for (Integer order : sortedTitles.keySet()) {
Title title = sortedTitles.get(order);
String answer = answers.get(order);
if ((answer == null && title.isDeleted())||(answer == null && nonExistentQuestions.contains(title.getTitleID()))) {
System.out.println("answer is null");
} else if (nonExistentQuestions.contains(title.getTitleID())) {
System.out.println("non-existent question~0");
} else if (title.isDeleted()) {
System.out.println("the question " + title.getTitleID() + " invalid~0");
} else if (answer == null) {
System.out.println("answer is null");
} else {
boolean isCorrect = judgeresults.size() >= order && judgeresults.get(order - 1);
System.out.println(title.getContent() + "~" + answer.trim() + "~" + isCorrect);
}
}
}
- 功能:输出每题的答案、判定结果,并考虑了多种题目状态(如无效题、缺失答案等)。
- 流程:
- 遍历试卷中的题目顺序,依次获取每道题的内容、学生答案、判定结果。
- 根据题目状态输出不同信息:
若题目被删除或不存在,则输出 "the question [ID] invalid~0" 或 "non-existent question~0"。
若答案为空,则输出 "answer is null"。
否则输出题目内容、学生答案及正确性。
- 方法
printScores
public void printScores(String studentID, String studentName) {
Map<Integer, Integer> scores = new LinkedHashMap<>();
int i = 0;
for (Integer order : testPaper.getTitles().keySet()) {
if (i < judgeresults.size() && judgeresults.get(i)) {
scores.put(order, testPaper.getScoreByTitleID(order));
} else {
scores.put(order, 0);
}
i++;
}
System.out.print(studentID + " " + studentName + ": ");
int total = 0;
int size = scores.size();
int count = 0;
for (Integer score : scores.values()) {
System.out.print(score);
total += score;
count++;
if (count < size) {
System.out.print(" ");
}
}
System.out.println("~" + total);
}
- 参数:学生ID和姓名。
- 功能:输出学生每题得分及总分。
- 流程:
- 遍历试卷题目,对已正确作答的题目,记录分数,否则得分为0。
- 格式化输出:先输出学生信息,再逐题输出分数,最后输出总分(
~total)。
三.采坑心得
7-5 答题判题程序-1
- 格式错误
问题:
printAnswersAndJudgeResults()方法中打印判断结果时最后一个结果后面有空格,会造成格式错误


解决方法:
- 确保最后一个判断结果输出后无空格

结果:


- 解析用户答案错误
问题:
saveAnswers(String[] UserAnswers)方法保存用户答案时没有正确解析输入的字符串,导致答案错误


解决方法:
- saveAnswers(String[] UserAnswers)方法保存用户答案时清除前面的#A:
String cleanedAnswer = UserAnswers[i].replace("#A:", "").trim(); // 去除 #A: 前缀并去掉空格
结果:


3.没有正确清除空格,造成非零返回
问题:
在输入时如果多输了空格,没有完全按照格式,代码又没有清除,会造成非零返回
例如输入时题目数量前面多了个空格:

造成非零返回

解决方法:
- 在输入和解析输入时使用trim()清除多余空格

结果:

7-4 答题判题程序-2
- 题目乱序
问题:
试卷题目的顺序与题号不一致造成答案输出不正确


解决方法:
- TestPaper类中保存题目使用LinkedHashMap<>,确保题目乱序不会影响最终处理顺序
private Map<Integer, Title> titles = new LinkedHashMap<>();
结果:


2.答案为空输出错误
问题:
答案为空时只打印answer is null,而不打印其他形式
修改前:
for (Integer titleID : sortedTitles.keySet()) {
Title title = sortedTitles.get(titleID);
String answer = i < answers.size() ? answers.get(i).trim() : "answer is null";
boolean isCorrect = judgeresults.get(i);
System.out.println(title.getContent() + "~" + answer + "~" + isCorrect);
i++;
}
输入:

输出:


解决方法:
- 在printAnswersAndJudgeResults()方法中增加输出判断,确保答案为空时只打印answer is null
for (Integer titleID : sortedTitles.keySet()) {
Title title = sortedTitles.get(titleID);
String answer = i < answers.size() ? answers.get(i).trim() : "answer is null";
if (answer.equals("answer is null")) {
System.out.println("answer is null");
} else {
boolean isCorrect = judgeresults.get(i);
System.out.println(title.getContent() + "~" + answer + "~" + isCorrect);
}
i++;
}
结果:


- 分值不足100提示信息
问题:
每次检测到试卷总分不为 100 时,立即输出警告,而不需要等到最后统一打印。
修改前:
// 将警告信息暂时存储在列表中
if (testPaper.getTotalScore() != 100) {
alerts.add("alert: full score of test paper" + testID + " is not 100 points");
}
// 解析所有试卷后,输出警告信息
for (String alert : alerts) {
System.out.println(alert);
}
结果:


解决方法:
- 将警告信息的输出位置从结尾移到解析试卷的过程中,当发现总分不为 100 时立刻打印。
修改后:
// 立即检查总分是否为100并输出警告
if (testPaper.getTotalScore() != 100) {
System.out.println("alert: full score of test paper" + testID + " is not 100 points");
}
结果:


7-3 答题判题程序-3
- 解析答案逻辑问题
问题:
答卷信息中如果答案为空,解析答案会解析出错误,会有空格问题
修改前:
// 解析答案,并在错误格式时提示
try {
for (String ans : userAnswers) {
String[] parts = ans.replaceFirst("^#A:", "").split("-");
if (parts.length != 2)
throw new IllegalArgumentException("wrong format: " + ans);
int questionOrder = Integer.parseInt(parts[0].trim());
String answerContent = parts[1].trim();
answers.put(questionOrder, answerContent); // 标注问题顺序到答案
}
judgeAnswers();
} catch (Exception e) {
System.out.println(e.getMessage());
}
输入:

输出:

解决方法:
- 添加判断逻辑
// 解析答案,并在错误格式时提示
try {
for (String ans : userAnswers) {
if (ans.trim().isEmpty()) {
continue; // 如果答案为空,则跳过
}
String[] parts = ans.replaceFirst("^#A:", "").split("-");
if (parts.length != 2)
throw new IllegalArgumentException("wrong format: " + ans);
int questionOrder = Integer.parseInt(parts[0].trim());
String answerContent = parts[1].trim();
answers.put(questionOrder, answerContent); // 标注问题顺序到答案
}
judgeAnswers();
} catch (Exception e) {
System.out.println(e.getMessage());
}
结果:

- 输出逻辑问题
问题:
如果答卷中的学号信息不在学生列表中,答案照常输出,判分时提示错误。
修改前:
if (!students.containsKey(studentID)) {
System.out.println(studentID + " not found");
continue;}
if (testPapers.containsKey(testID)) {
TestPaper testPaper = testPapers.get(testID);
AnswerSheet answerSheet = new AnswerSheet(testPaper, userAnswersWithOrder, nonExistentQuestions);
answerSheet.printAnswersAndJudgeResults();
answerSheet.printScores(studentID, studentName);
}
输入:

输出:

解决方法:
- 改变输出逻辑
if (testPapers.containsKey(testID)) {
TestPaper testPaper = testPapers.get(testID);
AnswerSheet answerSheet = new AnswerSheet(testPaper, userAnswersWithOrder, nonExistentQuestions);
answerSheet.printAnswersAndJudgeResults();
if (!students.containsKey(studentID)) {
System.out.println(studentID + " not found");
continue;}
answerSheet.printScores(studentID, studentName);
}
结果:

- 顺序号和题号混淆错误
问题:
答卷信息中格式:"#S:"+试卷号+" "+学号+" "+"#A:"+试卷题目的顺序号+"-"+答案内容+...
混淆了题号和顺序号照常答案错误
这里答卷处理用的题号:

对于这个输入:

错误解析处理:
题目信息:
题目编号为2,题目内容为 "2+2=",答案为 "4"。
题目编号为1,题目内容为 "1+1=",答案为 "2"。
试卷信息:
试卷编号为1,包含题目2和题目1,分别对应的分数都是5分。
学生信息:
学生ID为20201103,姓名为Tom。
答案信息:
学生ID为20201103 即 Tom 的答题卡,参与试卷编号为1,回答如下:
问题1的回答为 “5”
问题2的回答为 “4”
结合解析的内容,代码应进行如下处理:
判断试卷总分是否为100。由于试卷总分是10(5+5),所以会输出警告信息。
判断答案的正确性:
题目1的正确答案是 "2" ,回答 "5" 是错误的。
题目2的正确答案是 "4" ,回答 "4" 是正确的。
计算学生的得分:
题目1得分是0分(错误)。
题目2得分是5分(正确)。
总得分是5分。
所以,代码运行后输出:

解决方法:
- 使用顺序号

正确解析:
题目信息:
题目编号为2,题目内容为 "2+2=",答案为 "4"。
题目编号为1,题目内容为 "1+1=",答案为 "2"。
试卷信息:
试卷编号为1,包含题目编号为2的题目(在第一个位置,顺序号是1)和题目编号为1的题目(在第二个位置,顺序号是2),分别对应的分数都是5分。
学生信息:
学生ID为20201103,姓名为Tom。
答案信息:
学生ID为20201103 即 Tom 的答题卡,参与试卷编号为1,回答如下:
顺序号是1的题目的回答为 “5”
顺序号是2的题目的回答为 “4”
结合解析的内容,代码应进行如下处理:
判断试卷总分是否为100。由于试卷总分是10(5+5),所以会输出警告信息。
判断答案的正确性:
顺序号是1的题目,题目编号为2,正确答案是 "4" ,回答 "5" 是错误的。
顺序号是2的题目,题目编号为1,正确答案是 "2" ,回答 "4" 是错误的。
计算学生的得分:
顺序号是1的题目得分是0分(错误)。
顺序号是2的题目得分是0分(错误)。
总得分是0分。
所以,代码运行后应该输出:

4.优先级问题
问题:
如果答案输出时,一道题目同时出现答案不存在、引用错误题号、题目被删除,只提示一种信息,答案不存在的优先级最高
输入:

输出:

解决方法:
- 添加逻辑判断
if ((answer == null && title.isDeleted())||(answer == null && nonExistentQuestions.contains(title.getTitleID()))||(answer == null && nonExistentQuestions.contains(title.getTitleID())&&title.isDeleted())) {
System.out.println("answer is null");
}
结果:

四.改进建议
7-5 答题判题程序-1:
1. 输入格式的检查
问题:
- 如果输入格式不符合要求,可能会导致解析失败,并抛出异常,如
NumberFormatException或ArrayIndexOutOfBoundsException。
改进建议:
- 增加输入数据的检查,在解析题目或答案时进行错误处理。
- 使用
try-catch捕获异常,或者对输入数据提前进行验证。
2. 防止重复题目ID
问题:
- 如果题目ID重复,后添加的题目会覆盖之前的题目。
改进建议:
- 检查题目ID是否重复,并在添加时给出提示。
3. 用户答案与题目数量不匹配时的处理
问题:
- 如果用户提交的答案数量与题目数量不一致,程序会抛出
IndexOutOfBoundsException。
改进建议:
- 检查用户提交的答案数量是否匹配,给出适当的提示。
4. 去除答案前缀的优化
问题:
- 当前通过
replace("#A:", "")去除前缀,但多个#A:可能造成问题。
改进建议:
- 使用正则表达式准确匹配并去除前缀。
5. 标准答案比较逻辑的改进
问题:
- 只使用
equalsIgnoreCase进行比较,未去除多余空格。
改进建议:
- 在比较前去除答案中的多余空格,以提高容错性。
6. 简化代码结构,提高可读性
问题:
- 部分代码如
saveAnswers中存在冗余逻辑,可以简化。
改进建议:
- 将
titleID的计算移出循环,避免重复计算。 - 使用
for-each循环提高代码可读性。
7. 考虑异常处理的完整性
问题:
- 代码中部分地方没有考虑潜在的异常(如解析整数时的
NumberFormatException)。
改进建议:
- 使用
try-catch结构捕获异常,并给予用户友好的错误信息。
7-4 答题判题程序-2:
- 代码结构改进
问题:主方法 (main 方法) 中逻辑较长且混杂了不同的解析操作,影响可读性。
改进建议:可以将题目解析、试卷解析和答案解析封装成独立方法,降低 main 方法的复杂度,使代码更具模块化和易读性。
- 输入格式校验和错误处理
问题:代码假定输入格式是完全正确的,缺乏对输入格式错误的防御性检查。
改进建议:在解析 titlePattern、testPattern 和 answerPattern 时,增加对输入格式的验证,并在输入不符合预期时输出友好的错误信息。
- 字符串分割逻辑的改进
问题:在处理用户答案时,通过 split("\\s+#A:") 提取答案部分,但该方法可能会产生多余的空白项。
改进建议:可以直接使用 replaceFirst 去除前缀,并对答案数组提前进行去除空格操作,保证答案提取准确。
- 试卷总分警告的改进
问题:如果试卷总分不等于 100,程序输出警告,但未中断或进一步处理。
改进建议:可以添加一个标志记录不符合要求的试卷,并在全部解析后统一输出警告,方便查看。
- 回答解析与评分部分优化
问题:在 judgeAnswers 方法中,逐个解析题目和答案并记录得分,但如果答案数量少于题目数量,则程序会自动填入 false,可能会影响数据一致性。
改进建议:可以在用户**与题目数量不匹配时给出错误提示,确保用户输入与系统期望一致。
- 改进输出格式
问题:输出格式在某些情况下不够清晰,例如在回答和评分时没有详细标注问题编号和**。
改进建议:在输出时可以添加题目编号、**及其判定结果,使输出内容更具可读性。
- 提高代码的可复用性和维护性
问题:当前程序的某些方法较长且逻辑集中在一起,增加了代码的复杂度和未来维护的难度。
改进建议:将重复的分数计算逻辑提取为一个工具方法,简化主逻辑代码的复杂度。
- 加强代码的异常处理
问题:代码没有处理可能的异常(如 NumberFormatException),当输入不合法时会导致程序中断。
改进建议:使用 try-catch 结构包裹解析部分,并为用户提供更友好的错误提示信息。
7-3 答题判题程序-3:
- 重构数据结构
- 冗余数据减少:
orderToTitleID、titles和scores都包含相似的映射关系,可以使用单一的数据结构(如Map<Integer, Pair<Title, Integer>>)替代,以减少不必要的存储和数据同步问题。 - 嵌套数据访问的优化:在
AnswerSheet中可以通过创建List<AnswerResult>结构来存储每道题目的判断结果和得分状态。这种结构有助于将判断结果、题目信息和得分绑定在一起,减少后续的数据查找和同步成本。
- 优化判分逻辑
- 判断和计分方法合并:
judgeAnswers和printAnswersAndJudgeResults中重复了答题判分的逻辑。可以将它们合并,使judgeAnswers方法在完成题目判断时,直接生成每个题目的结果对象(如AnswerResult),并在printAnswersAndJudgeResults中直接遍历这些结果对象输出。 - 将非存在题目的判断提前:在构造
AnswerSheet时直接处理不存在的题目,可以减少每次判分中的重复判断逻辑,提高效率。
- 输入格式解析的改进
- 分离格式校验和业务逻辑:当前的输入解析、校验和业务处理都写在同一个
Main方法中。建议使用专门的解析类(如InputParser),将不同的匹配逻辑封装在方法中。这样不仅使代码更易读、易维护,而且在后续增加新格式或逻辑时更易扩展。 - 异常处理:在输入解析和答案解析时,使用了基本的异常处理机制。可以针对不同的格式错误添加详细的错误信息,并指明错误行,便于用户调试和查错。
- 提高可读性
- 方法名和变量名优化:例如,
printAnswersAndJudgeResults可以更具语义化地命名为displayAnswerEvaluation;judgeresults可以改为answerResults或类似更直观的名称。 - 移除硬编码输出:例如在
judgeAnswers中的System.out.println(e.getMessage());,可以定义常量或消息格式化工具,以便在多语言环境下复用代码。
- 改进用户反馈
- 更清晰的反馈信息:在处理学生答案时,若发现不存在的学生,可以提供更具体的信息。例如:
System.out.println("Student ID " + studentID + " not found in the system.");。 - 增加完整的答题反馈:目前,打印反馈中包含每个答案的对错标记,但未标注得分。可以增加对每个答案的得分输出,帮助用户更直观地了解答题得分情况。
- 增强代码鲁棒性
- 加强边界检查:在
printScores中,judgeresults.get(order - 1)的操作可能会导致索引越界错误,可以增加边界检查,或在judgeAnswers中提前填充judgeresults确保数量一致。 - 输入值校验:对于
testID、studentID等输入值,当前代码未对输入的合法性进行验证。可以在解析时添加有效性校验,避免非法输入导致异常崩溃。
五.总结
通过三次题目集的练习,我学会了如何通过方法来实现类的功能。我实现了多个类,将不同的逻辑封装在各自的类中,实现了职责分离,并通过构造函数和方法来封装数据和行为。这加深了我对类和对象的理解。我使用了Map、List和Set等集合类来存储和管理数据。通过TreeMap和LinkedHashMap来保持数据的顺序和唯一性,这让我对Java集合框架有了更深入的理解。我了解了如何将数据组织成更复杂的结构,例如将题目与分数关联,如何在答卷中记录用户答案及其判断结果。使用正则表达式解析输入数据,这让我认识到正则表达式在数据处理中的强大能力,能够有效地提取和验证字符串格式。我学会了如何从标准输入流中读取数据,并处理多行输入。这是处理用户输入的一种常见方式。理解了如何将复杂的逻辑分解成多个方法和类,使得代码更加模块化和可维护。我学会了如何处理异常情况,比如输入格式错误或找不到题目等。
需要进一步学习及研究的地方:虽然我在代码中实现了一些基本的面向对象原则,但我希望进一步学习设计模式,以提高代码的可重用性和可维护性。虽然已实现了一定的正则表达式解析,
但在复杂格式的解析和处理上仍需提升。例如,在解析题目和答案格式时,代码对于多层嵌套结构和错误处理的支持有限,可以研究更复杂的正则表达式或自定义解析器来提高精度。代码中对错误处理和异常报告的支持有待提升。未来学习嵌套的数据结构(如Map<Integer, List
对教师、课程、作业、实验、课上及课下组织方式等方面的改进建议及意见:多留点时间写PTA或者开放之前的PTA。