题目集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和姓名保存到students
Map。 -
调用关系:
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
(不存在的题目集合)。 - 功能:解析学生答案并存储到
answers
Map 中,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。